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 
37 namespace 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 //
49 class 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.
UnzipProcessor(const char * output_root,bool verbose,bool extract)54   UnzipProcessor(const char *output_root, bool verbose, bool extract)
55     : output_root_(output_root), verbose_(verbose), extract_(extract) {}
~UnzipProcessor()56   virtual ~UnzipProcessor() {}
57 
58   virtual void Process(const char* filename, const u4 attr,
59                        const u1* data, const size_t size);
Accept(const char * filename,const u4 attr)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
concat_path(char * out,const size_t size,const char * path1,const char * path2)72 void 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.
mkdirs(const char * path,mode_t mode)91 void 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 
Process(const char * filename,const u4 attr,const u1 * data,const size_t size)113 void 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.
basename(const char * path,char * output,size_t output_size)146 void 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)
extract(char * zipfile,bool verbose,bool extract)159 int 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
create(char * zipfile,char ** files,bool flatten,bool verbose,bool compress)180 int 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 //
usage(char * progname)255 static 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 
main(int argc,char ** argv)267 int 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