1// Copyright 2015 Google Inc. All rights reserved.
2//
3// Author: Alan Donovan <adonovan@google.com>
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//    http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17//
18// Zip / Unzip file using ijar zip implementation.
19//
20// Note that this Zip implementation intentionally don't compute CRC-32
21// because it is useless computation for jar because Java doesn't care.
22// CRC-32 of all files in the zip file will be set to 0.
23//
24
25#include <stdio.h>
26#include <string.h>
27#include <stdlib.h>
28#include <limits.h>
29#include <fcntl.h>
30#include <unistd.h>
31#include <sys/mman.h>
32#include <errno.h>
33#include <memory>
34
35#include "zip.h"
36
37namespace devtools_ijar {
38
39#define SYSCALL(expr)  do { \
40                         if ((expr) < 0) { \
41                           perror(#expr); \
42                           abort(); \
43                         } \
44                       } while (0)
45
46//
47// A ZipExtractorProcessor that extract all files in the ZIP file.
48//
49class UnzipProcessor : public ZipExtractorProcessor {
50 public:
51  // Create a processor who will extract the files into output_root
52  // if "extract" is set to true and will print the list of files and
53  // their unix modes if "verbose" is set to true.
54  UnzipProcessor(const char *output_root, bool verbose, bool extract)
55    : output_root_(output_root), verbose_(verbose), extract_(extract) {}
56  virtual ~UnzipProcessor() {}
57
58  virtual void Process(const char* filename, const u4 attr,
59                       const u1* data, const size_t size);
60  virtual bool Accept(const char* filename, const u4 attr) {
61    return true;
62  }
63
64 private:
65  const char *output_root_;
66  const bool verbose_;
67  const bool extract_;
68};
69
70// Concatene 2 path, path1 and path2, using / as a directory separator and
71// puting the result in "out". "size" specify the size of the output buffer
72void concat_path(char* out, const size_t size,
73                 const char *path1, const char *path2) {
74  int len1 = strlen(path1);
75  size_t l = len1;
76  strncpy(out, path1, size - 1);
77  out[size-1] = 0;
78  if (l < size - 1 && path1[len1] != '/' && path2[0] != '/') {
79    out[l] = '/';
80    l++;
81    out[l] = 0;
82  }
83  if (l < size - 1) {
84    strncat(out, path2, size - 1 - l);
85  }
86}
87
88// Do a recursive mkdir of all folders of path except the last path
89// segment (if path ends with a / then the last path segment is empty).
90// All folders are created using "mode" for creation mode.
91void mkdirs(const char *path, mode_t mode) {
92  char path_[PATH_MAX];
93  struct stat statst;
94  strncpy(path_, path, PATH_MAX);
95  path_[PATH_MAX-1] = 0;
96  char *pointer = path_;
97  while ((pointer = strchr(pointer, '/')) != NULL) {
98    if (path_ != pointer) {  // skip leading slash
99      *pointer = 0;
100      if (stat(path_, &statst) != 0) {
101        if (mkdir(path_, mode) < 0) {
102          fprintf(stderr, "Cannot create folder %s: %s\n",
103                  path_, strerror(errno));
104          abort();
105        }
106      }
107      *pointer = '/';
108    }
109    pointer++;
110  }
111}
112
113void UnzipProcessor::Process(const char* filename, const u4 attr,
114                             const u1* data, const size_t size) {
115  mode_t mode = zipattr_to_mode(attr);
116  mode_t perm = mode & 0777;
117  bool isdir = (mode & S_IFDIR) != 0;
118  if (attr == 0) {
119    // Fallback when the external attribute is not set.
120    isdir = filename[strlen(filename)-1] == '/';
121    perm = 0777;
122  }
123  if (verbose_) {
124    printf("%c %o %s\n", isdir ? 'd' : 'f', perm, filename);
125  }
126  if (extract_) {
127    char path[PATH_MAX];
128    int fd;
129    concat_path(path, PATH_MAX, output_root_, filename);
130    mkdirs(path, perm);
131    if (!isdir) {
132      fd = open(path, O_CREAT | O_WRONLY, perm);
133      if (fd < 0) {
134        fprintf(stderr, "Cannot open file %s for writing: %s\n",
135                path, strerror(errno));
136        abort();
137      }
138      SYSCALL(write(fd, data, size));
139      SYSCALL(close(fd));
140    }
141  }
142}
143
144// Get the basename of path and store it in output. output_size
145// is the size of the output buffer.
146void basename(const char *path, char *output, size_t output_size) {
147  const char *pointer = strrchr(path, '/');
148  if (pointer == NULL) {
149    pointer = path;
150  } else {
151    pointer++;  // Skip the leading slash.
152  }
153  strncpy(output, pointer, output_size);
154  output[output_size-1] = 0;
155}
156
157
158// Execute the extraction (or just listing if just v is provided)
159int extract(char *zipfile, bool verbose, bool extract) {
160  char output_root[PATH_MAX];
161  getcwd(output_root, PATH_MAX);
162
163  UnzipProcessor processor(output_root, verbose, extract);
164  std::unique_ptr<ZipExtractor> extractor(ZipExtractor::Create(zipfile,
165                                                               &processor));
166  if (extractor.get() == NULL) {
167    fprintf(stderr, "Unable to open zip file %s: %s.\n", zipfile,
168            strerror(errno));
169    return -1;
170  }
171
172  if (extractor->ProcessAll() < 0) {
173    fprintf(stderr, "%s.\n", extractor->GetError());
174    return -1;
175  }
176  return 0;
177}
178
179// Execute the create operation
180int create(char *zipfile, char **files, bool flatten, bool verbose,
181           bool compress) {
182  struct stat statst;
183  u8 size = ZipBuilder::EstimateSize(files);
184  if (size == 0) {
185    return -1;
186  }
187  std::unique_ptr<ZipBuilder> builder(ZipBuilder::Create(zipfile, size));
188  if (builder.get() == NULL) {
189    fprintf(stderr, "Unable to create zip file %s: %s.\n",
190            zipfile, strerror(errno));
191    return -1;
192  }
193  for (int i = 0; files[i] != NULL; i++) {
194    stat(files[i], &statst);
195    char path[PATH_MAX];
196    bool isdir = (statst.st_mode & S_IFDIR) != 0;
197
198    if (flatten && isdir) {
199      continue;
200    }
201
202    // Compute the path, flattening it if requested
203    if (flatten) {
204      basename(files[i], path, PATH_MAX);
205    } else {
206      strncpy(path, files[i], PATH_MAX);
207      path[PATH_MAX-1] = 0;
208      size_t len = strlen(path);
209      if (isdir && len < PATH_MAX - 1) {
210        // Add the trailing slash for folders
211        path[len] = '/';
212        path[len+1] = 0;
213      }
214    }
215
216    if (verbose) {
217      mode_t perm = statst.st_mode & 0777;
218      printf("%c %o %s\n", isdir ? 'd' : 'f', perm, path);
219    }
220
221    u1 *buffer = builder->NewFile(path, mode_to_zipattr(statst.st_mode));
222    if (isdir || statst.st_size == 0) {
223      builder->FinishFile(0);
224    } else {
225      // mmap the input file and memcpy
226      int fd = open(files[i], O_RDONLY);
227      if (fd < 0) {
228        fprintf(stderr, "Can't open file %s for reading: %s.\n",
229                files[i], strerror(errno));
230        return -1;
231      }
232      void *data = mmap(NULL, statst.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
233      if (data == MAP_FAILED) {
234        fprintf(stderr, "Can't mmap file %s for reading: %s.\n",
235                files[i], strerror(errno));
236        return -1;
237      }
238      memcpy(buffer, data, statst.st_size);
239      munmap(data, statst.st_size);
240      builder->FinishFile(statst.st_size, compress, true);
241    }
242  }
243  if (builder->Finish() < 0) {
244    fprintf(stderr, "%s\n", builder->GetError());
245    return -1;
246  }
247  return 0;
248}
249
250}  // namespace devtools_ijar
251
252//
253// main method
254//
255static void usage(char *progname) {
256  fprintf(stderr, "Usage: %s [vxc[fC]] x.zip [file1...filen]\n", progname);
257  fprintf(stderr, "  v verbose - list all file in x.zip\n");
258  fprintf(stderr, "  x extract - extract file in x.zip in current directory\n");
259  fprintf(stderr, "  c create  - add files to x.zip\n");
260  fprintf(stderr, "  f flatten - flatten files to use with create operation\n");
261  fprintf(stderr,
262          "  C compress - compress files when using the create operation\n");
263  fprintf(stderr, "x and c cannot be used in the same command-line.\n");
264  exit(1);
265}
266
267int main(int argc, char **argv) {
268  bool extract = false;
269  bool verbose = false;
270  bool create = false;
271  bool compress = false;
272  bool flatten = false;
273
274  if (argc < 3) {
275    usage(argv[0]);
276  }
277
278  for (int i = 0; argv[1][i] != 0; i++) {
279    switch (argv[1][i]) {
280    case 'x':
281      extract = true;
282      break;
283    case 'v':
284      verbose = true;
285      break;
286    case 'c':
287      create = true;
288      break;
289    case 'f':
290      flatten = true;
291      break;
292    case 'C':
293      compress = true;
294      break;
295    default:
296      usage(argv[0]);
297    }
298  }
299  if (create) {
300    if (extract) {
301      usage(argv[0]);
302    }
303    // Create a zip
304    return devtools_ijar::create(argv[2], argv + 3, flatten, verbose, compress);
305  } else {
306    if (flatten) {
307      usage(argv[0]);
308    }
309    // Extraction / list mode
310    return devtools_ijar::extract(argv[2], verbose, extract);
311  }
312}
313