OLD | NEW |
| (Empty) |
1 // Copyright 2014 The Chromium OS Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "volume_archive_libarchive.h" | |
6 | |
7 #include <algorithm> | |
8 #include <cerrno> | |
9 #include <limits> | |
10 | |
11 #include "archive_entry.h" | |
12 #include "ppapi/cpp/logging.h" | |
13 | |
14 namespace { | |
15 | |
16 const int64_t kArchiveReadDataError = -1; // Negative value means error. | |
17 | |
18 std::string ArchiveError(const std::string& message, archive* archive_object) { | |
19 return message + archive_error_string(archive_object); | |
20 } | |
21 | |
22 // Sets the libarchive internal error to a VolumeReader related error. | |
23 // archive_error_string function must work on valid strings, but in case of | |
24 // errors in the custom functions, libarchive API assumes the error is set by | |
25 // us. If we don't set it, we will get a Segmentation Fault because | |
26 // archive_error_string will work on invalid memory. | |
27 void SetLibarchiveErrorToVolumeReaderError(archive* archive_object) { | |
28 archive_set_error(archive_object, | |
29 EIO /* I/O error. */, | |
30 "%s" /* Format string similar to printf. */, | |
31 volume_archive_constants::kVolumeReaderError); | |
32 } | |
33 | |
34 ssize_t CustomArchiveRead(archive* archive_object, | |
35 void* client_data, | |
36 const void** buffer) { | |
37 VolumeArchiveLibarchive* volume_archive = | |
38 static_cast<VolumeArchiveLibarchive*>(client_data); | |
39 | |
40 int64_t read_bytes = volume_archive->reader()->Read( | |
41 volume_archive->reader_data_size(), buffer); | |
42 if (read_bytes == ARCHIVE_FATAL) | |
43 SetLibarchiveErrorToVolumeReaderError(archive_object); | |
44 return read_bytes; | |
45 } | |
46 | |
47 int64_t CustomArchiveSkip(archive* archive_object, | |
48 void* client_data, | |
49 int64_t request) { | |
50 VolumeArchiveLibarchive* volume_archive = | |
51 static_cast<VolumeArchiveLibarchive*>(client_data); | |
52 // VolumeReader::Skip returns 0 in case of failure and CustomArchiveRead is | |
53 // used instead, so there is no need to check for VolumeReader error. | |
54 return volume_archive->reader()->Skip(request); | |
55 } | |
56 | |
57 int64_t CustomArchiveSeek(archive* archive_object, | |
58 void* client_data, | |
59 int64_t offset, | |
60 int whence) { | |
61 VolumeArchiveLibarchive* volume_archive = | |
62 static_cast<VolumeArchiveLibarchive*>(client_data); | |
63 | |
64 int64_t new_offset = volume_archive->reader()->Seek(offset, whence); | |
65 if (new_offset == ARCHIVE_FATAL) | |
66 SetLibarchiveErrorToVolumeReaderError(archive_object); | |
67 | |
68 return new_offset; | |
69 } | |
70 | |
71 int CustomArchiveClose(archive* archive_object, void* client_data) { | |
72 return ARCHIVE_OK; | |
73 } | |
74 | |
75 const char* CustomArchivePassphrase( | |
76 archive* archive_object, void* client_data) { | |
77 VolumeArchiveLibarchive* volume_archive = | |
78 static_cast<VolumeArchiveLibarchive*>(client_data); | |
79 | |
80 return volume_archive->reader()->Passphrase(); | |
81 } | |
82 | |
83 } // namespace | |
84 | |
85 VolumeArchiveLibarchive::VolumeArchiveLibarchive(VolumeReader* reader) | |
86 : VolumeArchive(reader), | |
87 reader_data_size_(volume_archive_constants::kMinimumDataChunkSize), | |
88 archive_(NULL), | |
89 current_archive_entry_(NULL), | |
90 last_read_data_offset_(0), | |
91 last_read_data_length_(0), | |
92 decompressed_data_(NULL), | |
93 decompressed_data_size_(0), | |
94 decompressed_error_(false) { | |
95 } | |
96 | |
97 VolumeArchiveLibarchive::~VolumeArchiveLibarchive() { | |
98 Cleanup(); | |
99 } | |
100 | |
101 bool VolumeArchiveLibarchive::Init(const std::string& encoding) { | |
102 archive_ = archive_read_new(); | |
103 if (!archive_) { | |
104 set_error_message(volume_archive_constants::kArchiveReadNewError); | |
105 return false; | |
106 } | |
107 | |
108 // TODO(cmihail): Once the bug mentioned at | |
109 // https://github.com/libarchive/libarchive/issues/373 is resolved | |
110 // add RAR file handler to manifest.json. | |
111 if (archive_read_support_format_rar(archive_) != ARCHIVE_OK || | |
112 archive_read_support_format_zip_seekable(archive_) != ARCHIVE_OK) { | |
113 set_error_message(ArchiveError( | |
114 volume_archive_constants::kArchiveSupportErrorPrefix, archive_)); | |
115 return false; | |
116 } | |
117 | |
118 // Default encoding for file names in headers. Note, that another one may be | |
119 // used if specified in the archive. | |
120 std::string options = std::string("hdrcharset=") + encoding; | |
121 if (!encoding.empty() && | |
122 archive_read_set_options(archive_, options.c_str()) != ARCHIVE_OK) { | |
123 set_error_message(ArchiveError( | |
124 volume_archive_constants::kArchiveSupportErrorPrefix, archive_)); | |
125 return false; | |
126 } | |
127 | |
128 // Set callbacks for processing the archive's data and open the archive. | |
129 // The callback data is the VolumeArchive itself. | |
130 int ok = ARCHIVE_OK; | |
131 if (archive_read_set_read_callback(archive_, CustomArchiveRead) != ok || | |
132 archive_read_set_skip_callback(archive_, CustomArchiveSkip) != ok || | |
133 archive_read_set_seek_callback(archive_, CustomArchiveSeek) != ok || | |
134 archive_read_set_close_callback(archive_, CustomArchiveClose) != ok || | |
135 archive_read_set_passphrase_callback( | |
136 archive_, this, CustomArchivePassphrase) != ok || | |
137 archive_read_set_callback_data(archive_, this) != ok || | |
138 archive_read_open1(archive_) != ok) { | |
139 set_error_message(ArchiveError( | |
140 volume_archive_constants::kArchiveOpenErrorPrefix, archive_)); | |
141 return false; | |
142 } | |
143 | |
144 return true; | |
145 } | |
146 | |
147 VolumeArchive::Result VolumeArchiveLibarchive::GetNextHeader() { | |
148 // Headers are being read from the central directory (in the ZIP format), so | |
149 // use a large block size to save on IPC calls. The headers in EOCD are | |
150 // grouped one by one. | |
151 reader_data_size_ = volume_archive_constants::kMaximumDataChunkSize; | |
152 | |
153 // Reset to 0 for new VolumeArchive::ReadData operation. | |
154 last_read_data_offset_ = 0; | |
155 decompressed_data_size_ = 0; | |
156 | |
157 // Archive data is skipped automatically by next call to | |
158 // archive_read_next_header. | |
159 switch (archive_read_next_header(archive_, ¤t_archive_entry_)) { | |
160 case ARCHIVE_EOF: | |
161 return RESULT_EOF; | |
162 case ARCHIVE_OK: | |
163 return RESULT_SUCCESS; | |
164 default: | |
165 set_error_message(ArchiveError( | |
166 volume_archive_constants::kArchiveNextHeaderErrorPrefix, archive_)); | |
167 return RESULT_FAIL; | |
168 } | |
169 } | |
170 | |
171 VolumeArchive::Result VolumeArchiveLibarchive::GetNextHeader( | |
172 const char** pathname, | |
173 int64_t* size, | |
174 bool* is_directory, | |
175 time_t* modification_time) { | |
176 Result ret = GetNextHeader(); | |
177 | |
178 if (ret == RESULT_SUCCESS) { | |
179 *pathname = archive_entry_pathname(current_archive_entry_); | |
180 *size = archive_entry_size(current_archive_entry_); | |
181 *modification_time = archive_entry_mtime(current_archive_entry_); | |
182 *is_directory = archive_entry_filetype(current_archive_entry_) == AE_IFDIR; | |
183 } | |
184 | |
185 return ret; | |
186 } | |
187 | |
188 bool VolumeArchiveLibarchive::SeekHeader(int64_t index) { | |
189 // Reset to 0 for new VolumeArchive::ReadData operation. | |
190 last_read_data_offset_ = 0; | |
191 decompressed_data_size_ = 0; | |
192 | |
193 if (archive_read_seek_header(archive_, index) != ARCHIVE_OK) { | |
194 set_error_message(ArchiveError( | |
195 volume_archive_constants::kArchiveNextHeaderErrorPrefix, archive_)); | |
196 return false; | |
197 } | |
198 | |
199 return true; | |
200 } | |
201 | |
202 void VolumeArchiveLibarchive::DecompressData(int64_t offset, int64_t length) { | |
203 // TODO(cmihail): As an optimization consider using archive_read_data_block | |
204 // which avoids extra copying in case offset != last_read_data_offset_. | |
205 // The logic will be more complicated because archive_read_data_block offset | |
206 // will not be aligned with the offset of the read request from JavaScript. | |
207 | |
208 // Requests with offset smaller than last read offset are not supported. | |
209 if (offset < last_read_data_offset_) { | |
210 set_error_message( | |
211 std::string(volume_archive_constants::kArchiveReadDataErrorPrefix) + | |
212 "Reading backwards is not supported."); | |
213 decompressed_error_ = true; | |
214 return; | |
215 } | |
216 | |
217 // Request with offset greater than last read offset. Skip not needed bytes. | |
218 // Because files are compressed, seeking is not possible, so all of the bytes | |
219 // until the requested position must be unpacked. | |
220 ssize_t size = -1; | |
221 while (offset > last_read_data_offset_) { | |
222 // ReadData will call CustomArchiveRead when calling archive_read_data. Read | |
223 // should not request more bytes than possibly needed, so we request either | |
224 // offset - last_read_data_offset_, kMaximumDataChunkSize in case the former | |
225 // is too big or kMinimumDataChunkSize in case its too small and we might | |
226 // end up with too many IPCs. | |
227 reader_data_size_ = | |
228 std::max(std::min(offset - last_read_data_offset_, | |
229 volume_archive_constants::kMaximumDataChunkSize), | |
230 volume_archive_constants::kMinimumDataChunkSize); | |
231 | |
232 // No need for an offset in dummy_buffer as it will be ignored anyway. | |
233 // archive_read_data receives size_t as length parameter, but we limit it to | |
234 // volume_archive_constants::kDummyBufferSize which is positive and less | |
235 // than size_t maximum. So conversion from int64_t to size_t is safe here. | |
236 size = | |
237 archive_read_data(archive_, | |
238 dummy_buffer_, | |
239 std::min(offset - last_read_data_offset_, | |
240 volume_archive_constants::kDummyBufferSize)); | |
241 PP_DCHECK(size != 0); // The actual read is done below. We shouldn't get to | |
242 // end of file here. | |
243 if (size < 0) { // Error. | |
244 set_error_message(ArchiveError( | |
245 volume_archive_constants::kArchiveReadDataErrorPrefix, archive_)); | |
246 decompressed_error_ = true; | |
247 return; | |
248 } | |
249 last_read_data_offset_ += size; | |
250 } | |
251 | |
252 // Do not decompress more bytes than we can store internally. The | |
253 // kDecompressBufferSize limit is used to avoid huge memory usage. | |
254 int64_t left_length = | |
255 std::min(length, volume_archive_constants::kDecompressBufferSize); | |
256 | |
257 // ReadData will call CustomArchiveRead when calling archive_read_data. The | |
258 // read should be done with a value similar to length, which is the requested | |
259 // number of bytes, or kMaximumDataChunkSize / kMinimumDataChunkSize | |
260 // in case length is too big or too small. | |
261 reader_data_size_ = | |
262 std::max(std::min(static_cast<int64_t>(left_length), | |
263 volume_archive_constants::kMaximumDataChunkSize), | |
264 volume_archive_constants::kMinimumDataChunkSize); | |
265 | |
266 // Perform the actual copy. | |
267 int64_t bytes_read = 0; | |
268 do { | |
269 // archive_read_data receives size_t as length parameter, but we limit it to | |
270 // volume_archive_constants::kMinimumDataChunkSize (see left_length | |
271 // initialization), which is positive and less than size_t maximum. | |
272 // So conversion from int64_t to size_t is safe here. | |
273 size = archive_read_data( | |
274 archive_, decompressed_data_buffer_ + bytes_read, left_length); | |
275 if (size < 0) { // Error. | |
276 set_error_message(ArchiveError( | |
277 volume_archive_constants::kArchiveReadDataErrorPrefix, archive_)); | |
278 decompressed_error_ = true; | |
279 return; | |
280 } | |
281 bytes_read += size; | |
282 left_length -= size; | |
283 } while (left_length > 0 && size != 0); // There is still data to read. | |
284 | |
285 // VolumeArchiveLibarchive::DecompressData always stores the data from | |
286 // beginning of the buffer. VolumeArchiveLibarchive::ConsumeData is used | |
287 // to preserve the bytes that are decompressed but not required by | |
288 // VolumeArchiveLibarchive::ReadData. | |
289 decompressed_data_ = decompressed_data_buffer_; | |
290 decompressed_data_size_ = bytes_read; | |
291 } | |
292 | |
293 bool VolumeArchiveLibarchive::Cleanup() { | |
294 bool returnValue = true; | |
295 if (archive_ && archive_read_free(archive_) != ARCHIVE_OK) { | |
296 set_error_message(ArchiveError( | |
297 volume_archive_constants::kArchiveReadFreeErrorPrefix, archive_)); | |
298 returnValue = false; // Cleanup should release all resources even | |
299 // in case of failures. | |
300 } | |
301 archive_ = NULL; | |
302 | |
303 CleanupReader(); | |
304 | |
305 return returnValue; | |
306 } | |
307 | |
308 int64_t VolumeArchiveLibarchive::ReadData(int64_t offset, | |
309 int64_t length, | |
310 const char** buffer) { | |
311 PP_DCHECK(length > 0); // Length must be at least 1. | |
312 PP_DCHECK(current_archive_entry_); // Check that GetNextHeader was called at | |
313 // least once. In case it wasn't, this is | |
314 // a programmer error. | |
315 | |
316 // End of archive. | |
317 if (archive_entry_size_is_set(current_archive_entry_) && | |
318 archive_entry_size(current_archive_entry_) <= offset) | |
319 return 0; | |
320 | |
321 // In case of first read or no more available data in the internal buffer or | |
322 // offset is different from the last_read_data_offset_, then force | |
323 // VolumeArchiveLibarchive::DecompressData as the decompressed data is | |
324 // invalid. | |
325 if (!decompressed_data_ || last_read_data_offset_ != offset || | |
326 decompressed_data_size_ == 0) | |
327 DecompressData(offset, length); | |
328 | |
329 // Decompressed failed. | |
330 if (decompressed_error_) | |
331 return kArchiveReadDataError; | |
332 | |
333 last_read_data_length_ = length; // Used for decompress ahead. | |
334 | |
335 // Assign the output *buffer parameter to the internal buffer. | |
336 *buffer = decompressed_data_; | |
337 | |
338 // Advance internal buffer for next ReadData call. | |
339 int64_t read_bytes = std::min(decompressed_data_size_, length); | |
340 decompressed_data_ = decompressed_data_ + read_bytes; | |
341 decompressed_data_size_ -= read_bytes; | |
342 last_read_data_offset_ += read_bytes; | |
343 | |
344 PP_DCHECK(decompressed_data_ + decompressed_data_size_ <= | |
345 decompressed_data_buffer_ + | |
346 volume_archive_constants::kDecompressBufferSize); | |
347 | |
348 return read_bytes; | |
349 } | |
350 | |
351 void VolumeArchiveLibarchive::MaybeDecompressAhead() { | |
352 if (decompressed_data_size_ == 0) | |
353 DecompressData(last_read_data_offset_, last_read_data_length_); | |
354 } | |
OLD | NEW |