summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Lesinski2015-10-05 20:16:18 -0500
committerAdam Lesinski2015-10-15 18:27:44 -0500
commitad4ad8cfc8c1eada8356d554eeb0639ee48ef00d (patch)
treea05500fa9edfa366c0bda041e4f7a6c53e94c7a2 /libziparchive/zip_writer.cc
parentca0d66d597dde0a76adc3f5da15aedff4b6cb70b (diff)
downloadplatform-system-core-ad4ad8cfc8c1eada8356d554eeb0639ee48ef00d.tar.gz
platform-system-core-ad4ad8cfc8c1eada8356d554eeb0639ee48ef00d.tar.xz
platform-system-core-ad4ad8cfc8c1eada8356d554eeb0639ee48ef00d.zip
Implement ZipWriter for quickly writing ZipFiles.
The ZipWriter implementation exposes a stateful interface that allows bytes of data to be streamed in as they arrive. ZipEntries can be compressed and/or aligned on a 32-bit boundary for mmapping at runtime. Change-Id: I43ac9e661aa5022f00d9e12b247c4314d61c441c
Diffstat (limited to 'libziparchive/zip_writer.cc')
-rw-r--r--libziparchive/zip_writer.cc262
1 files changed, 262 insertions, 0 deletions
diff --git a/libziparchive/zip_writer.cc b/libziparchive/zip_writer.cc
new file mode 100644
index 000000000..de75d1ede
--- /dev/null
+++ b/libziparchive/zip_writer.cc
@@ -0,0 +1,262 @@
1/*
2 * Copyright (C) 2015 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17#include "entry_name_utils-inl.h"
18#include "zip_archive_common.h"
19#include "ziparchive/zip_writer.h"
20
21#include <cassert>
22#include <cstdio>
23#include <memory>
24#include <zlib.h>
25
26/* Zip compression methods we support */
27enum {
28 kCompressStored = 0, // no compression
29 kCompressDeflated = 8, // standard deflate
30};
31
32// No error, operation completed successfully.
33static const int32_t kNoError = 0;
34
35// The ZipWriter is in a bad state.
36static const int32_t kInvalidState = -1;
37
38// There was an IO error while writing to disk.
39static const int32_t kIoError = -2;
40
41// The zip entry name was invalid.
42static const int32_t kInvalidEntryName = -3;
43
44static const char* sErrorCodes[] = {
45 "Invalid state",
46 "IO error",
47 "Invalid entry name",
48};
49
50const char* ZipWriter::ErrorCodeString(int32_t error_code) {
51 if (error_code < 0 && (-error_code) < static_cast<int32_t>(arraysize(sErrorCodes))) {
52 return sErrorCodes[-error_code];
53 }
54 return nullptr;
55}
56
57ZipWriter::ZipWriter(FILE* f) : file_(f), current_offset_(0), state_(State::kWritingZip) {
58}
59
60ZipWriter::ZipWriter(ZipWriter&& writer) : file_(writer.file_),
61 current_offset_(writer.current_offset_),
62 state_(writer.state_),
63 files_(std::move(writer.files_)) {
64 writer.file_ = nullptr;
65 writer.state_ = State::kError;
66}
67
68ZipWriter& ZipWriter::operator=(ZipWriter&& writer) {
69 file_ = writer.file_;
70 current_offset_ = writer.current_offset_;
71 state_ = writer.state_;
72 files_ = std::move(writer.files_);
73 writer.file_ = nullptr;
74 writer.state_ = State::kError;
75 return *this;
76}
77
78int32_t ZipWriter::HandleError(int32_t error_code) {
79 state_ = State::kError;
80 return error_code;
81}
82
83int32_t ZipWriter::StartEntry(const char* path, size_t flags) {
84 return StartEntryWithTime(path, flags, time_t());
85}
86
87static void ExtractTimeAndDate(time_t when, uint16_t* out_time, uint16_t* out_date) {
88 /* round up to an even number of seconds */
89 when = static_cast<time_t>((static_cast<unsigned long>(when) + 1) & (~1));
90
91 struct tm* ptm;
92#if !defined(_WIN32)
93 struct tm tm_result;
94 ptm = localtime_r(&when, &tm_result);
95#else
96 ptm = localtime(&when);
97#endif
98
99 int year = ptm->tm_year;
100 if (year < 80) {
101 year = 80;
102 }
103
104 *out_date = (year - 80) << 9 | (ptm->tm_mon + 1) << 5 | ptm->tm_mday;
105 *out_time = ptm->tm_hour << 11 | ptm->tm_min << 5 | ptm->tm_sec >> 1;
106}
107
108int32_t ZipWriter::StartEntryWithTime(const char* path, size_t flags, time_t time) {
109 if (state_ != State::kWritingZip) {
110 return kInvalidState;
111 }
112
113 FileInfo fileInfo = {};
114 fileInfo.path = std::string(path);
115 fileInfo.local_file_header_offset = current_offset_;
116
117 if (!IsValidEntryName(reinterpret_cast<const uint8_t*>(fileInfo.path.data()),
118 fileInfo.path.size())) {
119 return kInvalidEntryName;
120 }
121
122 LocalFileHeader header = {};
123 header.lfh_signature = LocalFileHeader::kSignature;
124
125 // Set this flag to denote that a DataDescriptor struct will appear after the data,
126 // containing the crc and size fields.
127 header.gpb_flags |= kGPBDDFlagMask;
128
129 // For now, ignore the ZipWriter::kCompress flag.
130 fileInfo.compression_method = kCompressStored;
131 header.compression_method = fileInfo.compression_method;
132
133 ExtractTimeAndDate(time, &fileInfo.last_mod_time, &fileInfo.last_mod_date);
134 header.last_mod_time = fileInfo.last_mod_time;
135 header.last_mod_date = fileInfo.last_mod_date;
136
137 header.file_name_length = fileInfo.path.size();
138
139 off64_t offset = current_offset_ + sizeof(header) + fileInfo.path.size();
140 if ((flags & ZipWriter::kAlign32) && (offset & 0x03)) {
141 // Pad the extra field so the data will be aligned.
142 uint16_t padding = 4 - (offset % 4);
143 header.extra_field_length = padding;
144 offset += padding;
145 }
146
147 if (fwrite(&header, sizeof(header), 1, file_) != 1) {
148 return HandleError(kIoError);
149 }
150
151 if (fwrite(path, sizeof(*path), fileInfo.path.size(), file_) != fileInfo.path.size()) {
152 return HandleError(kIoError);
153 }
154
155 if (fwrite("\0\0\0", 1, header.extra_field_length, file_) != header.extra_field_length) {
156 return HandleError(kIoError);
157 }
158
159 files_.emplace_back(std::move(fileInfo));
160
161 current_offset_ = offset;
162 state_ = State::kWritingEntry;
163 return kNoError;
164}
165
166int32_t ZipWriter::WriteBytes(const void* data, size_t len) {
167 if (state_ != State::kWritingEntry) {
168 return HandleError(kInvalidState);
169 }
170
171 FileInfo& currentFile = files_.back();
172 if (currentFile.compression_method & kCompressDeflated) {
173 // TODO(adamlesinski): Implement compression using zlib deflate.
174 assert(false);
175 } else {
176 if (fwrite(data, 1, len, file_) != len) {
177 return HandleError(kIoError);
178 }
179 currentFile.crc32 = crc32(currentFile.crc32, reinterpret_cast<const Bytef*>(data), len);
180 currentFile.compressed_size += len;
181 current_offset_ += len;
182 }
183
184 currentFile.uncompressed_size += len;
185 return kNoError;
186}
187
188int32_t ZipWriter::FinishEntry() {
189 if (state_ != State::kWritingEntry) {
190 return kInvalidState;
191 }
192
193 const uint32_t sig = DataDescriptor::kOptSignature;
194 if (fwrite(&sig, sizeof(sig), 1, file_) != 1) {
195 state_ = State::kError;
196 return kIoError;
197 }
198
199 FileInfo& currentFile = files_.back();
200 DataDescriptor dd = {};
201 dd.crc32 = currentFile.crc32;
202 dd.compressed_size = currentFile.compressed_size;
203 dd.uncompressed_size = currentFile.uncompressed_size;
204 if (fwrite(&dd, sizeof(dd), 1, file_) != 1) {
205 return HandleError(kIoError);
206 }
207
208 current_offset_ += sizeof(DataDescriptor::kOptSignature) + sizeof(dd);
209 state_ = State::kWritingZip;
210 return kNoError;
211}
212
213int32_t ZipWriter::Finish() {
214 if (state_ != State::kWritingZip) {
215 return kInvalidState;
216 }
217
218 off64_t startOfCdr = current_offset_;
219 for (FileInfo& file : files_) {
220 CentralDirectoryRecord cdr = {};
221 cdr.record_signature = CentralDirectoryRecord::kSignature;
222 cdr.gpb_flags |= kGPBDDFlagMask;
223 cdr.compression_method = file.compression_method;
224 cdr.last_mod_time = file.last_mod_time;
225 cdr.last_mod_date = file.last_mod_date;
226 cdr.crc32 = file.crc32;
227 cdr.compressed_size = file.compressed_size;
228 cdr.uncompressed_size = file.uncompressed_size;
229 cdr.file_name_length = file.path.size();
230 cdr.local_file_header_offset = file.local_file_header_offset;
231 if (fwrite(&cdr, sizeof(cdr), 1, file_) != 1) {
232 return HandleError(kIoError);
233 }
234
235 if (fwrite(file.path.data(), 1, file.path.size(), file_) != file.path.size()) {
236 return HandleError(kIoError);
237 }
238
239 current_offset_ += sizeof(cdr) + file.path.size();
240 }
241
242 EocdRecord er = {};
243 er.eocd_signature = EocdRecord::kSignature;
244 er.disk_num = 1;
245 er.cd_start_disk = 1;
246 er.num_records_on_disk = files_.size();
247 er.num_records = files_.size();
248 er.cd_size = current_offset_ - startOfCdr;
249 er.cd_start_offset = startOfCdr;
250
251 if (fwrite(&er, sizeof(er), 1, file_) != 1) {
252 return HandleError(kIoError);
253 }
254
255 if (fflush(file_) != 0) {
256 return HandleError(kIoError);
257 }
258
259 current_offset_ += sizeof(er);
260 state_ = State::kDone;
261 return kNoError;
262}