C++ Utilities: io/archive.cpp Source File

C++ Utilities 5.33.0 Useful C++ classes and routines such as argument parser, IO and conversion utilities

Loading...
Searching...
No Matches
archive.cpp
Go to the documentation of this file.
1#include "./archive.h"
2
3#include "../conversion/stringbuilder.h"
4
5#include <archive.h>
6#include <archive_entry.h>
7
8#include <filesystem>
9#include <utility>
10
11using namespace CppUtilities;
12
13namespace CppUtilities {
14
18ArchiveException::~ArchiveException()
19{
20}
21
24struct AddDirectoryToFileMap {
25 bool operator()(std::string_view path)
26 {
27 fileMap[std::string(path)];
28 return false;
29 }
30 FileMap &fileMap;
31};
32
33struct AddFileToFileMap {
34 bool operator()(std::string_view directoryPath, ArchiveFile &&file)
35 {
36 fileMap[std::string(directoryPath)].emplace_back(std::move(file));
37 return false;
38 }
39 FileMap &fileMap;
40};
41
42struct ArchiveHandle {
43 explicit ArchiveHandle()
44 : handle(archive_read_new())
45 {
46 }
47
48 ~ArchiveHandle()
49 {
50 if (handle) {
51 archive_read_free(handle);
52 }
53 }
54
55 ArchiveHandle(const ArchiveHandle &) = delete;
56 ArchiveHandle(ArchiveHandle &&) = delete;
57
58 void close(std::string_view archiveName, std::string_view errorMessage)
59 {
60 if (!handle) {
61 return;
62 }
63 if (const auto returnCode = archive_read_free(std::exchange(handle, nullptr)); returnCode != ARCHIVE_OK) {
64 throw ArchiveException(errorMessage.empty() ? argsToString("Unable to free archive \"", archiveName, '\"')
65 : argsToString("Unable to free archive \"", archiveName, "\" after error: ", errorMessage));
66 }
67 }
68
69 operator struct archive *()
70 {
71 return handle;
72 }
73
74 struct archive *handle;
75};
76
77void walkThroughArchiveInternal(ArchiveHandle &ar, std::string_view archiveName, const FilePredicate &isFileRelevant, FileHandler &&fileHandler,
78 DirectoryHandler &&directoryHandler)
79{
80 // iterate through all archive entries
81 struct archive_entry *const entry = archive_entry_new();
82 auto fileContent = std::string();
83 while (archive_read_next_header2(ar, entry) == ARCHIVE_OK) {
84 // check entry type (only dirs, files and symlinks relevant here)
85 const auto entryType(archive_entry_filetype(entry));
86 if (entryType != AE_IFDIR && entryType != AE_IFREG && entryType != AE_IFLNK) {
87 continue;
88 }
89
90 // get file path
91 const char *filePath = archive_entry_pathname_utf8(entry);
92 if (!filePath) {
93 filePath = archive_entry_pathname(entry);
94 }
95 if (!filePath) {
96 continue;
97 }
98
99 // get permissions
100 const mode_t perm = archive_entry_perm(entry);
101
102 // add directories explicitly to get the entire tree though skipping irrelevant files
103 if (entryType == AE_IFDIR) {
104 if (!directoryHandler) {
105 continue;
106 }
107 // remove trailing slashes
108 const char *dirEnd = filePath;
109 for (const char *i = filePath; *i; ++i) {
110 if (*i != '/') {
111 dirEnd = i + 1;
112 }
113 }
114 if (directoryHandler(std::string_view(filePath, static_cast<std::size_t>(dirEnd - filePath)))) {
115 goto free;
116 }
117 continue;
118 }
119
120 // split the path into dir and fileName
121 const char *fileName = filePath, *dirEnd = filePath;
122 for (const char *i = filePath; *i; ++i) {
123 if (*i == '/') {
124 fileName = i + 1;
125 dirEnd = i;
126 }
127 }
128
129 // prevent looking into irrelevant files
130 if (isFileRelevant && !isFileRelevant(filePath, fileName, perm)) {
131 continue;
132 }
133
134 // read timestamps
135 const auto creationTime = DateTime::fromTimeStampGmt(archive_entry_ctime(entry));
136 const auto modificationTime = DateTime::fromTimeStampGmt(archive_entry_mtime(entry));
137
138 // read symlink
139 if (entryType == AE_IFLNK) {
140 if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
141 ArchiveFile(fileName, std::string(archive_entry_symlink_utf8(entry)), ArchiveFileType::Link, creationTime, modificationTime))) {
142 goto free;
143 }
144 continue;
145 }
146
147 // determine file size to pre-allocate buffer for file content
148 const la_int64_t fileSize = archive_entry_size(entry);
149 fileContent.clear();
150 if (fileSize > 0) {
151 fileContent.reserve(static_cast<std::string::size_type>(fileSize));
152 }
153
154 // read file content
155 const char *buff;
156 auto size = std::size_t();
157 auto offset = la_int64_t();
158 for (;;) {
159 const auto returnCode = archive_read_data_block(ar, reinterpret_cast<const void **>(&buff), &size, &offset);
160 if (returnCode == ARCHIVE_EOF || returnCode < ARCHIVE_OK) {
161 break;
162 }
163 fileContent.append(buff, size);
164 }
165
166 // move it to results
167 if (fileHandler(std::string_view(filePath, static_cast<std::string::size_type>(dirEnd - filePath)),
168 ArchiveFile(fileName, std::move(fileContent), ArchiveFileType::Regular, creationTime, modificationTime))) {
169 goto free;
170 }
171 }
172
173free:
174 // check for errors
175 const auto *const archiveError = archive_error_string(ar);
176 const auto errorMessage = archiveError ? std::string(archiveError) : std::string();
177
178 // free resources used by libarchive
179 archive_entry_free(entry);
180 ar.close(archiveName, errorMessage);
181 if (archiveError) {
182 throw ArchiveException(argsToString("An error occurred when reading archive \"", archiveName, "\": ", errorMessage));
183 }
184}
185
187
191 void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant,
192 FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
193{
194 // refuse opening empty buffer
195 if (archiveData.empty()) {
196 throw ArchiveException("Unable to open archive \"" % archiveName + "\": archive data is empty");
197 }
198 // open archive buffer using libarchive
199 auto ar = ArchiveHandle();
200 archive_read_support_filter_all(ar);
201 archive_read_support_format_all(ar);
202 const auto returnCode = archive_read_open_memory(ar, archiveData.data(), archiveData.size());
203 if (returnCode != ARCHIVE_OK) {
204 if (const char *const error = archive_error_string(ar)) {
205 throw ArchiveException("Unable to open/read archive \"" % archiveName % "\": " + error);
206 } else {
207 throw ArchiveException("Unable to open/read archive \"" % archiveName + "\": unable to open archive from memory");
208 }
209 }
210 walkThroughArchiveInternal(ar, archiveName, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
211}
212
216 FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant)
217{
218 auto results = FileMap();
219 walkThroughArchiveFromBuffer(archiveData, archiveName, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
220 return results;
221}
222
226 void walkThroughArchive(
227 std::string_view archivePath, const FilePredicate &isFileRelevant, FileHandler &&fileHandler, DirectoryHandler &&directoryHandler)
228{
229 // open archive file using libarchive
230 if (archivePath.empty()) {
231 throw ArchiveException("Unable to open archive: no path specified");
232 }
233 auto ec = std::error_code();
234 auto size = std::filesystem::file_size(archivePath, ec);
235 if (ec) {
236 throw ArchiveException("Unable to determine size of \"" % archivePath % "\": " + ec.message());
237 }
238 if (!size) {
239 throw ArchiveException("Unable to open archive \"" % archivePath + "\": file is empty");
240 }
241 auto ar = ArchiveHandle();
242 archive_read_support_filter_all(ar);
243 archive_read_support_format_all(ar);
244 const auto returnCode = archive_read_open_filename(ar, archivePath.data(), 10240);
245 if (returnCode != ARCHIVE_OK) {
246 if (const char *const error = archive_error_string(ar)) {
247 throw ArchiveException("Unable to open/read archive \"" % archivePath % "\": " + error);
248 } else {
249 throw ArchiveException("Unable to open/read archive \"" % archivePath + "\": unable to open archive from file");
250 }
251 }
252 walkThroughArchiveInternal(ar, archivePath, isFileRelevant, std::move(fileHandler), std::move(directoryHandler));
253}
254
258 FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant)
259{
260 auto results = FileMap();
261 walkThroughArchive(archivePath, isFileRelevant, AddFileToFileMap{ results }, AddDirectoryToFileMap{ results });
262 return results;
263}
264
265} // namespace CppUtilities
archive.h
CppUtilities::ArchiveException
The ArchiveException class is thrown by the various archiving-related functions of this library when ...
Definition archive.h:21
CppUtilities::ArchiveException::~ArchiveException
~ArchiveException() override
Destroys the ArchiveException.
Definition archive.cpp:18
CppUtilities::DateTime::fromTimeStampGmt
static constexpr DateTime fromTimeStampGmt(std::time_t timeStamp)
Constructs a new DateTime object with the GMT time from the specified UNIX timeStamp.
Definition datetime.h:272
CppUtilities
Contains all utilities provided by the c++utilities library.
Definition argumentparser.h:18
CppUtilities::walkThroughArchiveFromBuffer
CPP_UTILITIES_EXPORT void walkThroughArchiveFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant=FilePredicate(), FileHandler &&fileHandler=FileHandler(), DirectoryHandler &&directoryHandler=DirectoryHandler())
Invokes callbacks for files and directories in the specified archive.
Definition archive.cpp:191
CppUtilities::extractFilesFromBuffer
CPP_UTILITIES_EXPORT FileMap extractFilesFromBuffer(std::string_view archiveData, std::string_view archiveName, const FilePredicate &isFileRelevant=FilePredicate())
Extracts the specified archive.
Definition archive.cpp:216
CppUtilities::walkThroughArchive
CPP_UTILITIES_EXPORT void walkThroughArchive(std::string_view archivePath, const FilePredicate &isFileRelevant=FilePredicate(), FileHandler &&fileHandler=FileHandler(), DirectoryHandler &&directoryHandler=DirectoryHandler())
Invokes callbacks for files and directories in the specified archive.
Definition archive.cpp:226
CppUtilities::ArchiveFileType::Link
@ Link
Definition archive.h:47
CppUtilities::ArchiveFileType::Regular
@ Regular
Definition archive.h:47
CppUtilities::FilePredicate
std::function< bool(const char *, const char *, mode_t)> FilePredicate
A function that is invoked for each file within an archive. If it returns true, the file is considere...
Definition archive.h:80
CppUtilities::extractFiles
CPP_UTILITIES_EXPORT FileMap extractFiles(std::string_view archivePath, const FilePredicate &isFileRelevant=FilePredicate())
Extracts the specified archive.
Definition archive.cpp:258
CppUtilities::DirectoryHandler
std::function< bool(std::string_view path)> DirectoryHandler
A function that is invoked by the walk-through-functions to return a directory.
Definition archive.h:82
CppUtilities::FileMap
std::map< std::string, std::vector< ArchiveFile > > FileMap
A map of files extracted from an archive. Keys represent directories and values files within those di...
Definition archive.h:78
CppUtilities::argsToString
StringType argsToString(Args &&...args)
Definition stringbuilder.h:260
CppUtilities::FileHandler
std::function< bool(std::string_view path, ArchiveFile &&file)> FileHandler
A function that is invoked by the walk-through-functions to return a file.
Definition archive.h:84
CppUtilities::fileName
CPP_UTILITIES_EXPORT std::string fileName(const std::string &path)
Returns the file name and extension of the specified path string.
Definition path.cpp:15
stringbuilder.h
CppUtilities::ArchiveFile
The ArchiveFile class holds data about a file within an archive.
Definition archive.h:60
i
constexpr int i
Definition traitstests.cpp:106