/src/CMake/Source/cmArchiveWrite.cxx
Line | Count | Source |
1 | | /* Distributed under the OSI-approved BSD 3-Clause License. See accompanying |
2 | | file LICENSE.rst or https://cmake.org/licensing for details. */ |
3 | | #include "cmArchiveWrite.h" |
4 | | |
5 | | #include <cstdlib> |
6 | | #include <ctime> |
7 | | #include <iostream> |
8 | | #include <limits> |
9 | | #include <sstream> |
10 | | #include <string> |
11 | | #include <thread> |
12 | | |
13 | | #include <cm/algorithm> |
14 | | #include <cm/string_view> |
15 | | |
16 | | #include <cm3p/archive.h> |
17 | | #include <cm3p/archive_entry.h> |
18 | | |
19 | | #include "cmsys/Directory.hxx" |
20 | | #ifdef _WIN32 |
21 | | # include "cmsys/Encoding.hxx" |
22 | | #endif |
23 | | #include "cmsys/FStream.hxx" |
24 | | |
25 | | #include "cm_parse_date.h" |
26 | | |
27 | | #include "cmStringAlgorithms.h" |
28 | | #include "cmSystemTools.h" |
29 | | |
30 | | #ifndef __LA_SSIZE_T |
31 | | # define __LA_SSIZE_T la_ssize_t |
32 | | #endif |
33 | | |
34 | | static std::string cm_archive_error_string(struct archive* a) |
35 | 0 | { |
36 | 0 | char const* e = archive_error_string(a); |
37 | 0 | return e ? e : "unknown error"; |
38 | 0 | } |
39 | | |
40 | | // Set path to be written to the archive. |
41 | | static void cm_archive_entry_copy_pathname(struct archive_entry* e, |
42 | | char const* dest) |
43 | 0 | { |
44 | | #ifdef _WIN32 |
45 | | // libarchive converts our UTF-8 encoding to the archive's encoding. |
46 | | // `archive_entry_update_pathname_utf8` always populates the WCS form too. |
47 | | // It also populates the MBS form if possible, but we ignore conversion |
48 | | // failure because the archive formats support converting directly from |
49 | | // the WCS form to the archive's encoding without using the MBS form. |
50 | | archive_entry_update_pathname_utf8(e, dest); |
51 | | #else |
52 | | // libarchive converts our locale's encoding to the archive's encoding. |
53 | 0 | archive_entry_copy_pathname(e, dest); |
54 | 0 | #endif |
55 | 0 | } |
56 | | |
57 | | // Set path used for filesystem access. |
58 | | static void cm_archive_entry_copy_sourcepath(struct archive_entry* e, |
59 | | std::string const& file) |
60 | 0 | { |
61 | | #ifdef _WIN32 |
62 | | archive_entry_copy_sourcepath_w(e, cmsys::Encoding::ToWide(file).c_str()); |
63 | | #else |
64 | 0 | archive_entry_copy_sourcepath(e, file.c_str()); |
65 | 0 | #endif |
66 | 0 | } |
67 | | |
68 | | class cmArchiveWrite::Entry |
69 | | { |
70 | | struct archive_entry* Object; |
71 | | |
72 | | public: |
73 | | Entry() |
74 | 0 | : Object(archive_entry_new()) |
75 | 0 | { |
76 | 0 | } |
77 | 0 | ~Entry() { archive_entry_free(this->Object); } |
78 | | Entry(Entry const&) = delete; |
79 | | Entry& operator=(Entry const&) = delete; |
80 | 0 | operator struct archive_entry *() { return this->Object; } |
81 | | }; |
82 | | |
83 | | struct cmArchiveWrite::Callback |
84 | | { |
85 | | // archive_write_callback |
86 | | static __LA_SSIZE_T Write(struct archive* /*unused*/, void* cd, |
87 | | void const* b, size_t n) |
88 | 0 | { |
89 | 0 | cmArchiveWrite* self = static_cast<cmArchiveWrite*>(cd); |
90 | 0 | if (self->Stream.write(static_cast<char const*>(b), |
91 | 0 | static_cast<std::streamsize>(n))) { |
92 | 0 | return static_cast<__LA_SSIZE_T>(n); |
93 | 0 | } |
94 | 0 | return static_cast<__LA_SSIZE_T>(-1); |
95 | 0 | } |
96 | | }; |
97 | | |
98 | | cmArchiveWrite::cmArchiveWrite(std::ostream& os, Compress c, |
99 | | std::string const& format, |
100 | | std::string const& encoding, |
101 | | int compressionLevel, int numThreads) |
102 | 0 | : Stream(os) |
103 | 0 | , Archive(archive_write_new()) |
104 | 0 | , Disk(archive_read_disk_new()) |
105 | 0 | , Format(format) |
106 | 0 | { |
107 | | // Upstream fixed an issue with their integer parsing in 3.4.0 |
108 | | // which would cause spurious errors to be raised from `strtoull`. |
109 | |
|
110 | 0 | if (archive_write_set_format_by_name(this->Archive, format.c_str()) != |
111 | 0 | ARCHIVE_OK) { |
112 | 0 | this->Error = cmStrCat("archive_write_set_format_by_name: ", |
113 | 0 | cm_archive_error_string(this->Archive)); |
114 | 0 | return; |
115 | 0 | } |
116 | | |
117 | 0 | bool is7zip = (format == "7zip"); |
118 | 0 | bool isZip = (format == "zip"); |
119 | 0 | bool isFormatSupportsCompressionNatively = (is7zip || isZip); |
120 | |
|
121 | 0 | if (numThreads < 1) { |
122 | 0 | int upperLimit = (numThreads == 0) ? std::numeric_limits<int>::max() |
123 | 0 | : std::abs(numThreads); |
124 | |
|
125 | 0 | numThreads = |
126 | 0 | cm::clamp<int>(std::thread::hardware_concurrency(), 1, upperLimit); |
127 | 0 | } |
128 | |
|
129 | 0 | std::string sNumThreads = std::to_string(numThreads); |
130 | |
|
131 | 0 | if (!isFormatSupportsCompressionNatively) { |
132 | 0 | switch (c) { |
133 | 0 | case CompressNone: |
134 | 0 | if (archive_write_add_filter_none(this->Archive) != ARCHIVE_OK) { |
135 | 0 | this->Error = cmStrCat("archive_write_add_filter_none: ", |
136 | 0 | cm_archive_error_string(this->Archive)); |
137 | 0 | return; |
138 | 0 | } |
139 | 0 | break; |
140 | 0 | case CompressCompress: |
141 | 0 | if (archive_write_add_filter_compress(this->Archive) != ARCHIVE_OK) { |
142 | 0 | this->Error = cmStrCat("archive_write_add_filter_compress: ", |
143 | 0 | cm_archive_error_string(this->Archive)); |
144 | 0 | return; |
145 | 0 | } |
146 | 0 | break; |
147 | 0 | case CompressGZip: { |
148 | 0 | if (archive_write_add_filter_gzip(this->Archive) != ARCHIVE_OK) { |
149 | 0 | this->Error = cmStrCat("archive_write_add_filter_gzip: ", |
150 | 0 | cm_archive_error_string(this->Archive)); |
151 | 0 | return; |
152 | 0 | } |
153 | 0 | std::string source_date_epoch; |
154 | 0 | cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); |
155 | 0 | if (!source_date_epoch.empty()) { |
156 | | // We're not able to specify an arbitrary timestamp for gzip. |
157 | | // The next best thing is to omit the timestamp entirely. |
158 | 0 | if (archive_write_set_filter_option( |
159 | 0 | this->Archive, "gzip", "timestamp", nullptr) != ARCHIVE_OK) { |
160 | 0 | this->Error = cmStrCat("archive_write_set_filter_option: ", |
161 | 0 | cm_archive_error_string(this->Archive)); |
162 | 0 | return; |
163 | 0 | } |
164 | 0 | } |
165 | 0 | } break; |
166 | 0 | case CompressBZip2: |
167 | 0 | if (archive_write_add_filter_bzip2(this->Archive) != ARCHIVE_OK) { |
168 | 0 | this->Error = cmStrCat("archive_write_add_filter_bzip2: ", |
169 | 0 | cm_archive_error_string(this->Archive)); |
170 | 0 | return; |
171 | 0 | } |
172 | 0 | break; |
173 | 0 | case CompressLZMA: |
174 | 0 | if (archive_write_add_filter_lzma(this->Archive) != ARCHIVE_OK) { |
175 | 0 | this->Error = cmStrCat("archive_write_add_filter_lzma: ", |
176 | 0 | cm_archive_error_string(this->Archive)); |
177 | 0 | return; |
178 | 0 | } |
179 | 0 | break; |
180 | 0 | case CompressXZ: |
181 | 0 | if (archive_write_add_filter_xz(this->Archive) != ARCHIVE_OK) { |
182 | 0 | this->Error = cmStrCat("archive_write_add_filter_xz: ", |
183 | 0 | cm_archive_error_string(this->Archive)); |
184 | 0 | return; |
185 | 0 | } |
186 | | |
187 | 0 | #if ARCHIVE_VERSION_NUMBER >= 3004000 |
188 | | |
189 | | # ifdef _AIX |
190 | | // FIXME: Using more than 2 threads creates an empty archive. |
191 | | // Enforce this limit pending further investigation. |
192 | | if (numThreads > 2) { |
193 | | numThreads = 2; |
194 | | sNumThreads = std::to_string(numThreads); |
195 | | } |
196 | | # endif |
197 | 0 | if (archive_write_set_filter_option(this->Archive, "xz", "threads", |
198 | 0 | sNumThreads.c_str()) != |
199 | 0 | ARCHIVE_OK) { |
200 | 0 | this->Error = cmStrCat("archive_compressor_xz_options: ", |
201 | 0 | cm_archive_error_string(this->Archive)); |
202 | 0 | return; |
203 | 0 | } |
204 | 0 | #endif |
205 | | |
206 | 0 | break; |
207 | 0 | case CompressZstd: |
208 | 0 | if (archive_write_add_filter_zstd(this->Archive) != ARCHIVE_OK) { |
209 | 0 | this->Error = cmStrCat("archive_write_add_filter_zstd: ", |
210 | 0 | cm_archive_error_string(this->Archive)); |
211 | 0 | return; |
212 | 0 | } |
213 | | |
214 | 0 | #if ARCHIVE_VERSION_NUMBER >= 3006000 |
215 | 0 | if (archive_write_set_filter_option(this->Archive, "zstd", "threads", |
216 | 0 | sNumThreads.c_str()) != |
217 | 0 | ARCHIVE_OK) { |
218 | 0 | this->Error = cmStrCat("archive_compressor_zstd_options: ", |
219 | 0 | cm_archive_error_string(this->Archive)); |
220 | 0 | return; |
221 | 0 | } |
222 | 0 | #endif |
223 | 0 | break; |
224 | 0 | case CompressPPMd: |
225 | 0 | this->Error = cmStrCat("PPMd is not supported for ", format); |
226 | 0 | return; |
227 | 0 | } |
228 | 0 | } |
229 | | |
230 | | // 7zip always uses UTF16-LE for the headers and doesn't support |
231 | | // header encoding specification. |
232 | | // arbsd can use the default encoding of the system only. |
233 | 0 | if (!is7zip && format != "arbsd" && encoding != "OEM") { |
234 | 0 | char const* formatForOptions = format == "paxr" ? "pax" : format.c_str(); |
235 | 0 | if (archive_write_set_format_option(this->Archive, formatForOptions, |
236 | 0 | "hdrcharset", |
237 | 0 | encoding.c_str()) != ARCHIVE_OK) { |
238 | 0 | this->Error = cmStrCat("archive_write_set_format_option(hdrcharset): ", |
239 | 0 | cm_archive_error_string(this->Archive)); |
240 | 0 | return; |
241 | 0 | } |
242 | 0 | } |
243 | | |
244 | 0 | if (isFormatSupportsCompressionNatively || compressionLevel != 0) { |
245 | 0 | std::string compressionLevelStr = std::to_string(compressionLevel); |
246 | 0 | std::string archiveFilterName; |
247 | 0 | switch (c) { |
248 | 0 | case CompressNone: |
249 | 0 | if (is7zip || isZip) { |
250 | 0 | archiveFilterName = "store"; |
251 | 0 | } else { |
252 | | // Nothing to do - the value should be empty |
253 | 0 | } |
254 | 0 | break; |
255 | 0 | case CompressCompress: |
256 | 0 | if (is7zip || isZip) { |
257 | 0 | this->Error = |
258 | 0 | cmStrCat("CompressCompress is not supported for ", format); |
259 | 0 | } else { |
260 | | // Nothing to do - the value should be empty |
261 | 0 | } |
262 | 0 | break; |
263 | 0 | case CompressGZip: |
264 | 0 | if (is7zip || isZip) { |
265 | 0 | archiveFilterName = "deflate"; |
266 | 0 | } else { |
267 | 0 | archiveFilterName = "gzip"; |
268 | 0 | } |
269 | 0 | break; |
270 | 0 | case CompressBZip2: |
271 | | #if ARCHIVE_VERSION_NUMBER < 3008000 |
272 | | if (isZip) { |
273 | | this->Error = cmStrCat("BZip2 is not supported for ", format, |
274 | | ". Please, build CMake with libarchive 3.8.0 " |
275 | | "or newer if you want to use it."); |
276 | | return; |
277 | | } |
278 | | #endif |
279 | 0 | archiveFilterName = "bzip2"; |
280 | 0 | break; |
281 | 0 | case CompressLZMA: |
282 | | #if ARCHIVE_VERSION_NUMBER < 3008000 |
283 | | if (isZip) { |
284 | | this->Error = cmStrCat("LZMA is not supported for ", format, |
285 | | ". Please, build CMake with libarchive 3.8.0 " |
286 | | "or newer if you want to use it."); |
287 | | return; |
288 | | } |
289 | | #endif |
290 | 0 | if (is7zip) { |
291 | 0 | archiveFilterName = "lzma1"; |
292 | 0 | } else { |
293 | 0 | archiveFilterName = "lzma"; |
294 | 0 | } |
295 | 0 | break; |
296 | 0 | case CompressXZ: |
297 | | #if ARCHIVE_VERSION_NUMBER < 3008000 |
298 | | if (isZip) { |
299 | | this->Error = cmStrCat("LZMA2 (XZ) is not supported for ", format, |
300 | | ". Please, build CMake with libarchive 3.8.0 " |
301 | | "or newer if you want to use it."); |
302 | | return; |
303 | | } |
304 | | #endif |
305 | 0 | if (is7zip) { |
306 | 0 | archiveFilterName = "lzma2"; |
307 | 0 | } else { |
308 | 0 | archiveFilterName = "xz"; |
309 | 0 | } |
310 | 0 | break; |
311 | 0 | case CompressZstd: |
312 | | #if ARCHIVE_VERSION_NUMBER < 3008000 |
313 | | if (is7zip || isZip) { |
314 | | this->Error = cmStrCat("Zstd is not supported for ", format, |
315 | | ". Please, build CMake with libarchive 3.8.0 " |
316 | | "or newer if you want to use it."); |
317 | | return; |
318 | | } |
319 | | #endif |
320 | 0 | archiveFilterName = "zstd"; |
321 | 0 | break; |
322 | 0 | case CompressPPMd: |
323 | 0 | if (is7zip) { |
324 | 0 | archiveFilterName = "ppmd"; |
325 | 0 | } else { |
326 | 0 | this->Error = cmStrCat("PPMd is not supported for ", format); |
327 | 0 | } |
328 | 0 | return; |
329 | 0 | } |
330 | | |
331 | 0 | if (isFormatSupportsCompressionNatively) { |
332 | 0 | if (archiveFilterName.empty()) { |
333 | 0 | this->Error = cmStrCat("Unknown compression method for ", format); |
334 | 0 | return; |
335 | 0 | } |
336 | | |
337 | 0 | if (archive_write_set_format_option( |
338 | 0 | this->Archive, format.c_str(), "compression", |
339 | 0 | archiveFilterName.c_str()) != ARCHIVE_OK) { |
340 | 0 | this->Error = |
341 | 0 | cmStrCat("archive_write_set_format_option(compression): ", |
342 | 0 | cm_archive_error_string(this->Archive)); |
343 | 0 | return; |
344 | 0 | } |
345 | | |
346 | 0 | #if ARCHIVE_VERSION_NUMBER >= 3008000 |
347 | 0 | if (archive_write_set_format_option(this->Archive, format.c_str(), |
348 | 0 | "threads", |
349 | 0 | sNumThreads.c_str()) != ARCHIVE_OK) { |
350 | 0 | this->Error = cmStrCat("archive_write_set_format_option(threads): ", |
351 | 0 | cm_archive_error_string(this->Archive)); |
352 | 0 | return; |
353 | 0 | } |
354 | 0 | #endif |
355 | | |
356 | 0 | if (compressionLevel != 0) { |
357 | 0 | if (archive_write_set_format_option( |
358 | 0 | this->Archive, format.c_str(), "compression-level", |
359 | 0 | compressionLevelStr.c_str()) != ARCHIVE_OK) { |
360 | 0 | this->Error = |
361 | 0 | cmStrCat("archive_write_set_format_option(compression-level): ", |
362 | 0 | cm_archive_error_string(this->Archive)); |
363 | 0 | return; |
364 | 0 | } |
365 | 0 | } |
366 | 0 | } else if (compressionLevel != 0 && !archiveFilterName.empty()) { |
367 | 0 | if (archive_write_set_filter_option( |
368 | 0 | this->Archive, archiveFilterName.c_str(), "compression-level", |
369 | 0 | compressionLevelStr.c_str()) != ARCHIVE_OK) { |
370 | 0 | this->Error = cmStrCat("archive_write_set_filter_option: ", |
371 | 0 | cm_archive_error_string(this->Archive)); |
372 | 0 | return; |
373 | 0 | } |
374 | 0 | } |
375 | 0 | } |
376 | | |
377 | 0 | #if !defined(_WIN32) || defined(__CYGWIN__) |
378 | 0 | if (archive_read_disk_set_standard_lookup(this->Disk) != ARCHIVE_OK) { |
379 | 0 | this->Error = cmStrCat("archive_read_disk_set_standard_lookup: ", |
380 | 0 | cm_archive_error_string(this->Archive)); |
381 | 0 | return; |
382 | 0 | } |
383 | 0 | #endif |
384 | | |
385 | | // do not pad the last block!! |
386 | 0 | if (archive_write_set_bytes_in_last_block(this->Archive, 1)) { |
387 | 0 | this->Error = cmStrCat("archive_write_set_bytes_in_last_block: ", |
388 | 0 | cm_archive_error_string(this->Archive)); |
389 | 0 | return; |
390 | 0 | } |
391 | 0 | } |
392 | | |
393 | | bool cmArchiveWrite::Open() |
394 | 0 | { |
395 | 0 | if (!this->Error.empty()) { |
396 | 0 | return false; |
397 | 0 | } |
398 | 0 | if (archive_write_open( |
399 | 0 | this->Archive, this, nullptr, |
400 | 0 | reinterpret_cast<archive_write_callback*>(&Callback::Write), |
401 | 0 | nullptr) != ARCHIVE_OK) { |
402 | 0 | this->Error = |
403 | 0 | cmStrCat("archive_write_open: ", cm_archive_error_string(this->Archive)); |
404 | 0 | return false; |
405 | 0 | } |
406 | 0 | return true; |
407 | 0 | } |
408 | | |
409 | | bool cmArchiveWrite::SetExcludePatterns( |
410 | | std::vector<std::string> const& patterns) |
411 | 0 | { |
412 | 0 | if (patterns.empty()) { |
413 | 0 | return true; |
414 | 0 | } |
415 | 0 | if (!this->MatchObject) { |
416 | 0 | this->MatchObject = archive_match_new(); |
417 | 0 | if (!this->MatchObject) { |
418 | 0 | this->Error = "archive_match_new: out of memory"; |
419 | 0 | return false; |
420 | 0 | } |
421 | 0 | } |
422 | | // NOLINTNEXTLINE(readability-use-anyofallof) |
423 | 0 | for (std::string const& pattern : patterns) { |
424 | | // Reject empty patterns ourselves. libarchive would reject them too, but |
425 | | // only after allocating an error message on the match object that |
426 | | // archive_match_free() does not release. |
427 | 0 | if (pattern.empty()) { |
428 | 0 | this->Error = "exclusion pattern must not be empty"; |
429 | 0 | return false; |
430 | 0 | } |
431 | 0 | if (archive_match_exclude_pattern(this->MatchObject, pattern.c_str()) != |
432 | 0 | ARCHIVE_OK) { |
433 | 0 | this->Error = |
434 | 0 | cmStrCat("Failed to add to exclusion list: ", pattern, " (", |
435 | 0 | cm_archive_error_string(this->MatchObject), ')'); |
436 | 0 | return false; |
437 | 0 | } |
438 | 0 | } |
439 | 0 | return true; |
440 | 0 | } |
441 | | |
442 | | cmArchiveWrite::~cmArchiveWrite() |
443 | 0 | { |
444 | 0 | if (this->MatchObject) { |
445 | 0 | archive_match_free(this->MatchObject); |
446 | 0 | } |
447 | 0 | archive_read_free(this->Disk); |
448 | 0 | archive_write_free(this->Archive); |
449 | 0 | } |
450 | | |
451 | | bool cmArchiveWrite::Add(std::string path, size_t skip, char const* prefix, |
452 | | bool recursive) |
453 | 0 | { |
454 | 0 | if (!path.empty() && path.back() == '/') { |
455 | 0 | path.erase(path.size() - 1); |
456 | 0 | } |
457 | 0 | this->AddPath(path, skip, prefix, recursive); |
458 | 0 | return this->Okay(); |
459 | 0 | } |
460 | | |
461 | | bool cmArchiveWrite::AddPath(std::string const& path, size_t skip, |
462 | | char const* prefix, bool recursive) |
463 | 0 | { |
464 | | // Skip paths whose archive entry name matches an exclusion pattern. For a |
465 | | // directory this also prevents descending into it, pruning the subtree. |
466 | 0 | if (this->MatchObject && skip < path.length()) { |
467 | 0 | cm::string_view out = cm::string_view(path).substr(skip); |
468 | 0 | std::string dest = cmStrCat(prefix ? prefix : "", out); |
469 | 0 | Entry e; |
470 | 0 | cm_archive_entry_copy_pathname(e, dest.c_str()); |
471 | 0 | int matched = archive_match_path_excluded(this->MatchObject, e); |
472 | 0 | if (matched < 0) { |
473 | 0 | this->Error = cmStrCat("archive_match_path_excluded: ", |
474 | 0 | cm_archive_error_string(this->MatchObject)); |
475 | 0 | return false; |
476 | 0 | } |
477 | 0 | if (matched > 0) { |
478 | 0 | return true; |
479 | 0 | } |
480 | 0 | } |
481 | 0 | if (path != "." || (this->Format != "zip" && this->Format != "7zip")) { |
482 | 0 | if (!this->AddFile(path, skip, prefix)) { |
483 | 0 | return false; |
484 | 0 | } |
485 | 0 | } |
486 | 0 | if ((!cmSystemTools::FileIsDirectory(path) || !recursive) || |
487 | 0 | cmSystemTools::FileIsSymlink(path)) { |
488 | 0 | return true; |
489 | 0 | } |
490 | 0 | cmsys::Directory d; |
491 | 0 | if (d.Load(path)) { |
492 | 0 | std::string next = cmStrCat(path, '/'); |
493 | 0 | if (next == "./" && (this->Format == "zip" || this->Format == "7zip")) { |
494 | 0 | next.clear(); |
495 | 0 | } |
496 | 0 | std::string::size_type end = next.size(); |
497 | 0 | unsigned long n = d.GetNumberOfFiles(); |
498 | 0 | for (unsigned long i = 0; i < n; ++i) { |
499 | 0 | std::string const& file = d.GetFileName(i); |
500 | 0 | if (file != "." && file != "..") { |
501 | 0 | next.erase(end); |
502 | 0 | next += file; |
503 | 0 | if (!this->AddPath(next, skip, prefix)) { |
504 | 0 | return false; |
505 | 0 | } |
506 | 0 | } |
507 | 0 | } |
508 | 0 | } |
509 | 0 | return true; |
510 | 0 | } |
511 | | |
512 | | bool cmArchiveWrite::AddFile(std::string const& file, size_t skip, |
513 | | char const* prefix) |
514 | 0 | { |
515 | 0 | this->Error = ""; |
516 | | // Skip the file if we have no name for it. This may happen on a |
517 | | // top-level directory, which does not need to be included anyway. |
518 | 0 | if (skip >= file.length()) { |
519 | 0 | return true; |
520 | 0 | } |
521 | 0 | cm::string_view out = cm::string_view(file).substr(skip); |
522 | | |
523 | | // Meta-data. |
524 | 0 | std::string dest = cmStrCat(prefix ? prefix : "", out); |
525 | 0 | if (this->Verbose) { |
526 | 0 | std::cout << dest << "\n"; |
527 | 0 | } |
528 | 0 | Entry e; |
529 | 0 | cm_archive_entry_copy_sourcepath(e, file); |
530 | 0 | cm_archive_entry_copy_pathname(e, dest.c_str()); |
531 | 0 | if (archive_read_disk_entry_from_file(this->Disk, e, -1, nullptr) != |
532 | 0 | ARCHIVE_OK) { |
533 | 0 | this->Error = |
534 | 0 | cmStrCat("Unable to read from file:\n ", file, "\nbecause:\n ", |
535 | 0 | cm_archive_error_string(this->Disk)); |
536 | 0 | return false; |
537 | 0 | } |
538 | 0 | if (!this->MTime.empty()) { |
539 | 0 | time_t now; |
540 | 0 | time(&now); |
541 | 0 | time_t t = cm_parse_date(now, this->MTime.c_str()); |
542 | 0 | if (t == -1) { |
543 | 0 | this->Error = cmStrCat("unable to parse mtime '", this->MTime, '\''); |
544 | 0 | return false; |
545 | 0 | } |
546 | 0 | archive_entry_set_mtime(e, t, 0); |
547 | 0 | } else { |
548 | 0 | std::string source_date_epoch; |
549 | 0 | cmSystemTools::GetEnv("SOURCE_DATE_EPOCH", source_date_epoch); |
550 | 0 | if (!source_date_epoch.empty()) { |
551 | 0 | std::istringstream iss(source_date_epoch); |
552 | 0 | time_t epochTime; |
553 | 0 | iss >> epochTime; |
554 | 0 | if (iss.eof() && !iss.fail()) { |
555 | | // Set all of the file times to the epoch time to handle archive |
556 | | // formats that include creation/access time. |
557 | 0 | archive_entry_set_mtime(e, epochTime, 0); |
558 | 0 | archive_entry_set_atime(e, epochTime, 0); |
559 | 0 | archive_entry_set_ctime(e, epochTime, 0); |
560 | 0 | archive_entry_set_birthtime(e, epochTime, 0); |
561 | 0 | } |
562 | 0 | } |
563 | 0 | } |
564 | | |
565 | | // manages the uid/guid of the entry (if any) |
566 | 0 | if (this->Uid.IsSet() && this->Gid.IsSet()) { |
567 | 0 | archive_entry_set_uid(e, this->Uid.Get()); |
568 | 0 | archive_entry_set_gid(e, this->Gid.Get()); |
569 | 0 | } |
570 | |
|
571 | 0 | if (!this->Uname.empty() && !this->Gname.empty()) { |
572 | 0 | archive_entry_set_uname(e, this->Uname.c_str()); |
573 | 0 | archive_entry_set_gname(e, this->Gname.c_str()); |
574 | 0 | } |
575 | | |
576 | | // manages the permissions |
577 | 0 | if (this->Permissions.IsSet()) { |
578 | 0 | archive_entry_set_perm(e, this->Permissions.Get()); |
579 | 0 | } |
580 | |
|
581 | 0 | if (this->PermissionsMask.IsSet()) { |
582 | 0 | int perm = archive_entry_perm(e); |
583 | 0 | archive_entry_set_perm(e, perm & this->PermissionsMask.Get()); |
584 | 0 | } |
585 | | |
586 | | // Clear acl and xattr fields not useful for distribution. |
587 | 0 | archive_entry_acl_clear(e); |
588 | 0 | archive_entry_xattr_clear(e); |
589 | 0 | archive_entry_set_fflags(e, 0, 0); |
590 | |
|
591 | 0 | if (this->Format == "pax" || this->Format == "paxr") { |
592 | | // Sparse files are a GNU tar extension. |
593 | | // Do not use them in standard tar files. |
594 | 0 | archive_entry_sparse_clear(e); |
595 | 0 | } |
596 | |
|
597 | 0 | if (archive_write_header(this->Archive, e) != ARCHIVE_OK) { |
598 | 0 | this->Error = cmStrCat("archive_write_header: ", |
599 | 0 | cm_archive_error_string(this->Archive)); |
600 | 0 | return false; |
601 | 0 | } |
602 | | |
603 | | // do not copy content of symlink |
604 | 0 | if (!archive_entry_symlink(e)) { |
605 | | // Content. |
606 | 0 | if (size_t size = static_cast<size_t>(archive_entry_size(e))) { |
607 | 0 | return this->AddData(file, size); |
608 | 0 | } |
609 | 0 | } |
610 | 0 | return true; |
611 | 0 | } |
612 | | |
613 | | bool cmArchiveWrite::AddData(std::string const& file, size_t size) |
614 | 0 | { |
615 | 0 | cmsys::ifstream fin(file.c_str(), std::ios::in | std::ios::binary); |
616 | 0 | if (!fin) { |
617 | 0 | this->Error = cmStrCat("Error opening \"", file, |
618 | 0 | "\": ", cmSystemTools::GetLastSystemError()); |
619 | 0 | return false; |
620 | 0 | } |
621 | | |
622 | 0 | char buffer[16384]; |
623 | 0 | size_t nleft = size; |
624 | 0 | while (nleft > 0) { |
625 | 0 | using ssize_type = std::streamsize; |
626 | 0 | size_t const nnext = nleft > sizeof(buffer) ? sizeof(buffer) : nleft; |
627 | 0 | ssize_type const nnext_s = static_cast<ssize_type>(nnext); |
628 | 0 | fin.read(buffer, nnext_s); |
629 | | // Some stream libraries (older HPUX) return failure at end of |
630 | | // file on the last read even if some data were read. Check |
631 | | // gcount instead of trusting the stream error status. |
632 | 0 | if (static_cast<size_t>(fin.gcount()) != nnext) { |
633 | 0 | break; |
634 | 0 | } |
635 | 0 | if (archive_write_data(this->Archive, buffer, nnext) != nnext_s) { |
636 | 0 | this->Error = cmStrCat("archive_write_data: ", |
637 | 0 | cm_archive_error_string(this->Archive)); |
638 | 0 | return false; |
639 | 0 | } |
640 | 0 | nleft -= nnext; |
641 | 0 | } |
642 | 0 | if (nleft > 0) { |
643 | 0 | this->Error = cmStrCat("Error reading \"", file, |
644 | 0 | "\": ", cmSystemTools::GetLastSystemError()); |
645 | 0 | return false; |
646 | 0 | } |
647 | 0 | return true; |
648 | 0 | } |