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