blob: 51aea54157ccd540a86b227ba2fb9b4f4b4db0f3 [file] [log] [blame]
Damien Martin-Guillerezbf6281d2015-11-19 16:41:33 +00001// Copyright 2015 The Bazel Authors. All rights reserved.
Damien Martin-Guillerez08441122015-05-28 11:12:31 +00002//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15//
16// Zip / Unzip file using ijar zip implementation.
17//
18// Note that this Zip implementation intentionally don't compute CRC-32
19// because it is useless computation for jar because Java doesn't care.
20// CRC-32 of all files in the zip file will be set to 0.
21//
22
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000023#include <errno.h>
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +000024#include <limits.h>
25#include <stdint.h>
26#include <stdio.h>
27#include <stdlib.h>
28#include <string.h>
Laszlo Csomord2ed0692016-12-01 14:04:35 +000029
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000030#include <memory>
Rumou Duan7942bdc2016-05-12 15:31:07 +000031#include <set>
32#include <string>
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000033
Laszlo Csomor645dbc42016-12-01 12:56:43 +000034#include "third_party/ijar/platform_utils.h"
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000035#include "third_party/ijar/zip.h"
36
37namespace devtools_ijar {
38
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000039//
Rumou Duan7942bdc2016-05-12 15:31:07 +000040// A ZipExtractorProcessor that extract files in the ZIP file.
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000041//
42class UnzipProcessor : public ZipExtractorProcessor {
43 public:
Rumou Duan7942bdc2016-05-12 15:31:07 +000044 // Create a processor who will extract the given files (or all files if NULL)
45 // into output_root if "extract" is set to true and will print the list of
46 // files and their unix modes if "verbose" is set to true.
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +000047 UnzipProcessor(const char *output_root, char **files, bool verbose,
Laszlo Csomor87425682016-10-04 10:28:54 +000048 bool extract) : output_root_(output_root),
49 verbose_(verbose),
50 extract_(extract) {
Rumou Duan7942bdc2016-05-12 15:31:07 +000051 if (files != NULL) {
52 for (int i = 0; files[i] != NULL; i++) {
53 file_names.insert(std::string(files[i]));
54 }
55 }
56 }
57
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000058 virtual ~UnzipProcessor() {}
59
60 virtual void Process(const char* filename, const u4 attr,
61 const u1* data, const size_t size);
62 virtual bool Accept(const char* filename, const u4 attr) {
Rumou Duan7942bdc2016-05-12 15:31:07 +000063 // All entry files are accepted by default.
64 if (file_names.empty()) {
65 return true;
66 } else {
67 // If users have specified file entries, only accept those files.
68 return file_names.count(std::string(filename)) == 1;
69 }
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000070 }
71
72 private:
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +000073 const char *output_root_;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000074 const bool verbose_;
75 const bool extract_;
Rumou Duan7942bdc2016-05-12 15:31:07 +000076 std::set<std::string> file_names;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000077};
78
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +000079// Concatene 2 path, path1 and path2, using / as a directory separator and
80// puting the result in "out". "size" specify the size of the output buffer
81void concat_path(char* out, const size_t size,
82 const char *path1, const char *path2) {
83 int len1 = strlen(path1);
84 size_t l = len1;
85 strncpy(out, path1, size - 1);
86 out[size-1] = 0;
87 if (l < size - 1 && path1[len1] != '/' && path2[0] != '/') {
88 out[l] = '/';
89 l++;
90 out[l] = 0;
91 }
92 if (l < size - 1) {
93 strncat(out, path2, size - 1 - l);
94 }
95}
96
Damien Martin-Guillerez08441122015-05-28 11:12:31 +000097void UnzipProcessor::Process(const char* filename, const u4 attr,
98 const u1* data, const size_t size) {
Laszlo Csomor479e18d2016-12-02 15:11:08 +000099 mode_t perm = zipattr_to_perm(attr);
100 bool isdir = zipattr_is_dir(attr);
Damien Martin-Guillerez0cd6dfb2015-06-29 11:06:50 +0000101 if (attr == 0) {
102 // Fallback when the external attribute is not set.
103 isdir = filename[strlen(filename)-1] == '/';
104 perm = 0777;
105 }
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000106 if (verbose_) {
Damien Martin-Guillereza1c73f92015-05-29 12:27:25 +0000107 printf("%c %o %s\n", isdir ? 'd' : 'f', perm, filename);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000108 }
109 if (extract_) {
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +0000110 char path[PATH_MAX];
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +0000111 concat_path(path, PATH_MAX, output_root_, filename);
Laszlo Csomorb8caca02016-12-01 14:37:46 +0000112 if (!make_dirs(path, perm) ||
113 (!isdir && !write_file(path, perm, data, size))) {
Laszlo Csomor8d6da002016-12-01 13:16:13 +0000114 abort();
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000115 }
116 }
117}
118
Laszlo Csomor87425682016-10-04 10:28:54 +0000119// Get the basename of path and store it in output. output_size
120// is the size of the output buffer.
121void basename(const char *path, char *output, size_t output_size) {
122 const char *pointer = strrchr(path, '/');
123 if (pointer == NULL) {
124 pointer = path;
125 } else {
126 pointer++; // Skip the leading slash.
127 }
128 strncpy(output, pointer, output_size);
129 output[output_size-1] = 0;
130}
131
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000132// Execute the extraction (or just listing if just v is provided)
Rumou Duan7942bdc2016-05-12 15:31:07 +0000133int extract(char *zipfile, char* exdir, char **files, bool verbose,
134 bool extract) {
Laszlo Csomor8457f3f2016-12-01 14:25:18 +0000135 std::string cwd = get_cwd();
136 if (cwd.empty()) {
Thiago Farina74d179a2016-04-05 09:30:51 +0000137 return -1;
138 }
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000139
Laszlo Csomor9f3f6de2016-10-04 10:55:43 +0000140 char output_root[PATH_MAX];
Rumou Duan7942bdc2016-05-12 15:31:07 +0000141 if (exdir != NULL) {
Laszlo Csomor8457f3f2016-12-01 14:25:18 +0000142 concat_path(output_root, PATH_MAX, cwd.c_str(), exdir);
Rumou Duan7942bdc2016-05-12 15:31:07 +0000143 } else {
Laszlo Csomor8457f3f2016-12-01 14:25:18 +0000144 strncpy(output_root, cwd.c_str(), PATH_MAX);
Rumou Duan7942bdc2016-05-12 15:31:07 +0000145 }
146
147 UnzipProcessor processor(output_root, files, verbose, extract);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000148 std::unique_ptr<ZipExtractor> extractor(ZipExtractor::Create(zipfile,
149 &processor));
150 if (extractor.get() == NULL) {
151 fprintf(stderr, "Unable to open zip file %s: %s.\n", zipfile,
152 strerror(errno));
153 return -1;
154 }
155
156 if (extractor->ProcessAll() < 0) {
157 fprintf(stderr, "%s.\n", extractor->GetError());
158 return -1;
159 }
160 return 0;
161}
162
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000163// add a file to the zip
164int add_file(std::unique_ptr<ZipBuilder> const &builder, char *file,
Yun Peng43302f42016-08-04 13:48:02 +0000165 char *zip_path, bool flatten, bool verbose, bool compress) {
Laszlo Csomor479e18d2016-12-02 15:11:08 +0000166 Stat file_stat = {0, 0666, false};
Yun Peng43302f42016-08-04 13:48:02 +0000167 if (file != NULL) {
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000168 if (!stat_file(file, &file_stat)) {
László Csomor9a4dffe2016-12-12 09:59:38 +0000169 fprintf(stderr, "Cannot stat file %s: %s\n", file, strerror(errno));
Yun Peng43302f42016-08-04 13:48:02 +0000170 return -1;
171 }
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000172 }
Yun Peng30702662016-08-05 11:16:05 +0000173 char *final_path = zip_path != NULL ? zip_path : file;
Yun Peng43302f42016-08-04 13:48:02 +0000174
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000175 bool isdir = file_stat.is_directory;
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000176
177 if (flatten && isdir) {
178 return 0;
179 }
180
181 // Compute the path, flattening it if requested
Laszlo Csomor87425682016-10-04 10:28:54 +0000182 char path[PATH_MAX];
Yun Peng43302f42016-08-04 13:48:02 +0000183 size_t len = strlen(final_path);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000184 if (len > PATH_MAX) {
Yun Peng43302f42016-08-04 13:48:02 +0000185 fprintf(stderr, "Path too long: %s.\n", final_path);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000186 return -1;
187 }
188 if (flatten) {
Laszlo Csomor87425682016-10-04 10:28:54 +0000189 basename(final_path, path, PATH_MAX);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000190 } else {
Laszlo Csomor87425682016-10-04 10:28:54 +0000191 strncpy(path, final_path, PATH_MAX);
192 path[PATH_MAX - 1] = 0;
193 if (isdir && len < PATH_MAX - 1) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000194 // Add the trailing slash for folders
Laszlo Csomor87425682016-10-04 10:28:54 +0000195 path[len] = '/';
196 path[len + 1] = 0;
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000197 }
198 }
199
200 if (verbose) {
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000201 mode_t perm = file_stat.file_mode & 0777;
Laszlo Csomor87425682016-10-04 10:28:54 +0000202 printf("%c %o %s\n", isdir ? 'd' : 'f', perm, path);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000203 }
204
Laszlo Csomor479e18d2016-12-02 15:11:08 +0000205 u1 *buffer = builder->NewFile(path, stat_to_zipattr(file_stat));
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000206 if (isdir || file_stat.total_size == 0) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000207 builder->FinishFile(0);
208 } else {
Laszlo Csomord2ed0692016-12-01 14:04:35 +0000209 if (!read_file(file, buffer, file_stat.total_size)) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000210 return -1;
211 }
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000212 builder->FinishFile(file_stat.total_size, compress, true);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000213 }
214 return 0;
215}
216
217// Read a list of files separated by newlines. The resulting array can be
218// freed using the free method.
219char **read_filelist(char *filename) {
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000220 Stat file_stat;
221 if (!stat_file(filename, &file_stat)) {
László Csomor9a4dffe2016-12-12 09:59:38 +0000222 fprintf(stderr, "Cannot stat file %s: %s\n", filename, strerror(errno));
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000223 return NULL;
224 }
225
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000226 char *data = static_cast<char *>(malloc(file_stat.total_size));
Laszlo Csomord2ed0692016-12-01 14:04:35 +0000227 if (!read_file(filename, data, file_stat.total_size)) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000228 return NULL;
229 }
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000230
231 int nb_entries = 1;
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000232 for (int i = 0; i < file_stat.total_size; i++) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000233 if (data[i] == '\n') {
234 nb_entries++;
235 }
236 }
237
238 size_t sizeof_array = sizeof(char *) * (nb_entries + 1);
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000239 void *result = malloc(sizeof_array + file_stat.total_size);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000240 // copy the content
241 char **filelist = static_cast<char **>(result);
242 char *content = static_cast<char *>(result) + sizeof_array;
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000243 memcpy(content, data, file_stat.total_size);
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000244 free(data);
245 // Create the corresponding array
246 int j = 1;
247 filelist[0] = content;
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000248 for (int i = 0; i < file_stat.total_size; i++) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000249 if (content[i] == '\n') {
250 content[i] = 0;
Laszlo Csomor645dbc42016-12-01 12:56:43 +0000251 if (i + 1 < file_stat.total_size) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000252 filelist[j] = content + i + 1;
253 j++;
254 }
255 }
256 }
257 filelist[j] = NULL;
258 return filelist;
259}
260
Yun Peng43302f42016-08-04 13:48:02 +0000261// return real paths of the files
262char **parse_filelist(char *zipfile, char **file_entries, int nb_entries,
263 bool flatten) {
264 // no need to free since the path lists should live until the end of the
265 // program
266 char **files = static_cast<char **>(malloc(sizeof(char *) * nb_entries));
267 char **zip_paths = file_entries;
268 for (int i = 0; i < nb_entries; i++) {
269 char *p_eq = strchr(file_entries[i], '=');
270 if (p_eq != NULL) {
271 if (flatten) {
272 fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile,
273 "= can't be used with flatten");
274 free(files);
275 return NULL;
276 }
277 if (p_eq == file_entries[i]) {
278 fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile,
279 "A zip path should be given before =");
280 free(files);
281 return NULL;
282 }
283 *p_eq = '\0';
284 files[i] = p_eq + 1;
285 if (files[i][0] == '\0') {
286 files[i] = NULL;
287 }
288 } else {
289 files[i] = file_entries[i];
290 zip_paths[i] = NULL;
291 }
292 }
293 return files;
294}
295
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000296// Execute the create operation
Yun Peng43302f42016-08-04 13:48:02 +0000297int create(char *zipfile, char **file_entries, bool flatten, bool verbose,
Damien Martin-Guillerez3a160e72015-08-31 12:22:14 +0000298 bool compress) {
Yun Peng43302f42016-08-04 13:48:02 +0000299 int nb_entries = 0;
300 while (file_entries[nb_entries] != NULL) {
301 nb_entries++;
302 }
303 char **zip_paths = file_entries;
304 char **files = parse_filelist(zipfile, file_entries, nb_entries, flatten);
305 if (files == NULL) {
306 return -1;
307 }
308
309 u8 size = ZipBuilder::EstimateSize(files, zip_paths, nb_entries);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000310 if (size == 0) {
311 return -1;
312 }
313 std::unique_ptr<ZipBuilder> builder(ZipBuilder::Create(zipfile, size));
314 if (builder.get() == NULL) {
315 fprintf(stderr, "Unable to create zip file %s: %s.\n",
316 zipfile, strerror(errno));
317 return -1;
318 }
Yun Peng43302f42016-08-04 13:48:02 +0000319
320 for (int i = 0; i < nb_entries; i++) {
321 if (add_file(builder, files[i], zip_paths[i], flatten, verbose, compress) <
322 0) {
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000323 return -1;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000324 }
325 }
326 if (builder->Finish() < 0) {
327 fprintf(stderr, "%s\n", builder->GetError());
328 return -1;
329 }
330 return 0;
331}
332
333} // namespace devtools_ijar
334
335//
336// main method
337//
338static void usage(char *progname) {
Yun Peng43302f42016-08-04 13:48:02 +0000339 fprintf(stderr,
Yun Peng30702662016-08-05 11:16:05 +0000340 "Usage: %s [vxc[fC]] x.zip [-d exdir] [[zip_path1=]file1 ... "
341 "[zip_pathn=]filen]\n",
Rumou Duan7942bdc2016-05-12 15:31:07 +0000342 progname);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000343 fprintf(stderr, " v verbose - list all file in x.zip\n");
Rumou Duan7942bdc2016-05-12 15:31:07 +0000344 fprintf(stderr,
345 " x extract - extract files in x.zip to current directory, or "
346 " an optional directory relative to the current directory "
347 " specified through -d option\n");
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000348 fprintf(stderr, " c create - add files to x.zip\n");
349 fprintf(stderr, " f flatten - flatten files to use with create operation\n");
Damien Martin-Guillerez3a160e72015-08-31 12:22:14 +0000350 fprintf(stderr,
351 " C compress - compress files when using the create operation\n");
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000352 fprintf(stderr, "x and c cannot be used in the same command-line.\n");
Yun Peng30702662016-08-05 11:16:05 +0000353 fprintf(stderr,
354 "\nFor every file, a path in the zip can be specified. Examples:\n");
Yun Peng43302f42016-08-04 13:48:02 +0000355 fprintf(stderr,
356 " zipper c x.zip a/b/__init__.py= # Add an empty file at "
357 "a/b/__init__.py\n");
358 fprintf(stderr,
359 " zipper c x.zip a/b/main.py=foo/bar/bin.py # Add file "
360 "foo/bar/bin.py at a/b/main.py\n");
Yun Peng30702662016-08-05 11:16:05 +0000361 fprintf(stderr,
362 "\nIf the zip path is not specified, it is assumed to be the file "
363 "path.\n");
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000364 exit(1);
365}
366
367int main(int argc, char **argv) {
368 bool extract = false;
369 bool verbose = false;
370 bool create = false;
Damien Martin-Guillerez3a160e72015-08-31 12:22:14 +0000371 bool compress = false;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000372 bool flatten = false;
373
374 if (argc < 3) {
375 usage(argv[0]);
376 }
377
378 for (int i = 0; argv[1][i] != 0; i++) {
379 switch (argv[1][i]) {
380 case 'x':
381 extract = true;
382 break;
383 case 'v':
384 verbose = true;
385 break;
386 case 'c':
387 create = true;
388 break;
389 case 'f':
390 flatten = true;
391 break;
Damien Martin-Guillerez3a160e72015-08-31 12:22:14 +0000392 case 'C':
393 compress = true;
394 break;
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000395 default:
396 usage(argv[0]);
397 }
398 }
Rumou Duan7942bdc2016-05-12 15:31:07 +0000399
400 // x and c cannot be used in the same command-line.
401 if (create && extract) {
402 usage(argv[0]);
403 }
404
405 // Calculate the argument index of the first entry file.
406 int filelist_start_index;
407 if (argc > 3 && strcmp(argv[3], "-d") == 0) {
408 filelist_start_index = 5;
409 } else {
410 filelist_start_index = 3;
411 }
412
413 char** filelist = NULL;
414
415 // We have one option file. Read and extract the content.
416 if (argc == filelist_start_index + 1 &&
417 argv[filelist_start_index][0] == '@') {
418 char* filelist_name = argv[filelist_start_index];
419 filelist = devtools_ijar::read_filelist(filelist_name + 1);
420 if (filelist == NULL) {
421 fprintf(stderr, "Can't read file list %s: %s.\n", filelist_name,
422 strerror(errno));
423 return -1;
424 }
425 // We have more than one files. Assume that they are all file entries.
426 } else if (argc >= filelist_start_index + 1) {
427 filelist = argv + filelist_start_index;
428 } else {
429 // There are no entry files specified. This is forbidden if we are creating
430 // a zip file.
431 if (create) {
432 fprintf(stderr, "Can't create zip without input files specified.");
433 return -1;
434 }
435 }
436
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000437 if (create) {
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000438 // Create a zip
Damien Martin-Guillerezc88508c2016-01-21 14:19:07 +0000439 return devtools_ijar::create(argv[2], filelist, flatten, verbose, compress);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000440 } else {
441 if (flatten) {
442 usage(argv[0]);
443 }
Rumou Duan7942bdc2016-05-12 15:31:07 +0000444
445 char* exdir = NULL;
446 if (argc > 3 && strcmp(argv[3], "-d") == 0) {
447 exdir = argv[4];
448 }
449
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000450 // Extraction / list mode
Rumou Duan7942bdc2016-05-12 15:31:07 +0000451 return devtools_ijar::extract(argv[2], exdir, filelist, verbose, extract);
Damien Martin-Guillerez08441122015-05-28 11:12:31 +0000452 }
453}