// Copyright 2015 The Bazel Authors. All rights reserved. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. // You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // // Unless required by applicable law or agreed to in writing, software // distributed under the License is distributed on an "AS IS" BASIS, // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. // See the License for the specific language governing permissions and // limitations under the License. // // Zip / Unzip file using ijar zip implementation. // // Note that this Zip implementation intentionally don't compute CRC-32 // because it is useless computation for jar because Java doesn't care. // CRC-32 of all files in the zip file will be set to 0. // #include #include #include #include #include #include #include #include #include #include "third_party/ijar/platform_utils.h" #include "third_party/ijar/zip.h" namespace devtools_ijar { // // A ZipExtractorProcessor that extract files in the ZIP file. // class UnzipProcessor : public ZipExtractorProcessor { public: // Create a processor who will extract the given files (or all files if NULL) // into output_root if "extract" is set to true and will print the list of // files and their unix modes if "verbose" is set to true. UnzipProcessor(const char *output_root, char **files, bool verbose, bool extract, bool flatten) : output_root_(output_root), verbose_(verbose), extract_(extract), flatten_(flatten) { if (files != NULL) { for (int i = 0; files[i] != NULL; i++) { file_names.insert(std::string(files[i])); } } } virtual ~UnzipProcessor() {} virtual void Process(const char* filename, const u4 attr, const u1* data, const size_t size); virtual bool Accept(const char* filename, const u4 attr) { // All entry files are accepted by default. if (file_names.empty()) { return true; } else { // If users have specified file entries, only accept those files. return file_names.count(std::string(filename)) == 1; } } private: const char *output_root_; const bool verbose_; const bool extract_; const bool flatten_; std::set file_names; }; // Concatene 2 path, path1 and path2, using / as a directory separator and // puting the result in "out". "size" specify the size of the output buffer void concat_path(char* out, const size_t size, const char *path1, const char *path2) { int len1 = strlen(path1); size_t l = len1; strncpy(out, path1, size - 1); out[size-1] = 0; if (l < size - 1 && path1[len1] != '/' && path2[0] != '/') { out[l] = '/'; l++; out[l] = 0; } if (l < size - 1) { strncat(out, path2, size - 1 - l); } } void UnzipProcessor::Process(const char* filename, const u4 attr, const u1* data, const size_t size) { mode_t perm = zipattr_to_perm(attr); bool isdir = zipattr_is_dir(attr); const char *output_file_name = filename; if (attr == 0) { // Fallback when the external attribute is not set. isdir = filename[strlen(filename)-1] == '/'; perm = 0777; } if (flatten_) { if (isdir) { return; } const char *p = strrchr(filename, '/'); if (p != NULL) { output_file_name = p + 1; } } if (verbose_) { printf("%c %o %s\n", isdir ? 'd' : 'f', perm, output_file_name); } if (extract_) { char path[PATH_MAX]; concat_path(path, PATH_MAX, output_root_, output_file_name); if (!make_dirs(path, perm) || (!isdir && !write_file(path, perm, data, size))) { abort(); } } } // Get the basename of path and store it in output. output_size // is the size of the output buffer. void basename(const char *path, char *output, size_t output_size) { const char *pointer = strrchr(path, '/'); if (pointer == NULL) { pointer = path; } else { pointer++; // Skip the leading slash. } strncpy(output, pointer, output_size); output[output_size-1] = 0; } // Execute the extraction (or just listing if just v is provided) int extract(char *zipfile, char *exdir, char **files, bool verbose, bool extract, bool flatten) { std::string cwd = get_cwd(); if (cwd.empty()) { return -1; } char output_root[PATH_MAX]; if (exdir != NULL) { concat_path(output_root, PATH_MAX, cwd.c_str(), exdir); } else { strncpy(output_root, cwd.c_str(), PATH_MAX); } UnzipProcessor processor(output_root, files, verbose, extract, flatten); std::unique_ptr extractor(ZipExtractor::Create(zipfile, &processor)); if (extractor.get() == NULL) { fprintf(stderr, "Unable to open zip file %s: %s.\n", zipfile, strerror(errno)); return -1; } if (extractor->ProcessAll() < 0) { fprintf(stderr, "%s.\n", extractor->GetError()); return -1; } return 0; } // add a file to the zip int add_file(std::unique_ptr const &builder, char *file, char *zip_path, bool flatten, bool verbose, bool compress) { Stat file_stat = {0, 0666, false}; if (file != NULL) { if (!stat_file(file, &file_stat)) { fprintf(stderr, "Cannot stat file %s: %s\n", file, strerror(errno)); return -1; } } char *final_path = zip_path != NULL ? zip_path : file; bool isdir = file_stat.is_directory; if (flatten && isdir) { return 0; } // Compute the path, flattening it if requested char path[PATH_MAX]; size_t len = strlen(final_path); if (len > PATH_MAX) { fprintf(stderr, "Path too long: %s.\n", final_path); return -1; } if (flatten) { basename(final_path, path, PATH_MAX); } else { strncpy(path, final_path, PATH_MAX); path[PATH_MAX - 1] = 0; if (isdir && len < PATH_MAX - 1) { // Add the trailing slash for folders path[len] = '/'; path[len + 1] = 0; } } if (verbose) { mode_t perm = file_stat.file_mode & 0777; printf("%c %o %s\n", isdir ? 'd' : 'f', perm, path); } u1 *buffer = builder->NewFile(path, stat_to_zipattr(file_stat)); if (isdir || file_stat.total_size == 0) { builder->FinishFile(0); } else { if (!read_file(file, buffer, file_stat.total_size)) { return -1; } builder->FinishFile(file_stat.total_size, compress, true); } return 0; } // Read a list of files separated by newlines. The resulting array can be // freed using the free method. char **read_filelist(char *filename) { Stat file_stat; if (!stat_file(filename, &file_stat)) { fprintf(stderr, "Cannot stat file %s: %s\n", filename, strerror(errno)); return NULL; } char *data = static_cast(malloc(file_stat.total_size)); if (!read_file(filename, data, file_stat.total_size)) { return NULL; } int nb_entries = 1; for (int i = 0; i < file_stat.total_size; i++) { if (data[i] == '\n') { nb_entries++; } } size_t sizeof_array = sizeof(char *) * (nb_entries + 1); void *result = malloc(sizeof_array + file_stat.total_size); // copy the content char **filelist = static_cast(result); char *content = static_cast(result) + sizeof_array; memcpy(content, data, file_stat.total_size); free(data); // Create the corresponding array int j = 1; filelist[0] = content; for (int i = 0; i < file_stat.total_size; i++) { if (content[i] == '\n') { content[i] = 0; if (i + 1 < file_stat.total_size) { filelist[j] = content + i + 1; j++; } } } filelist[j] = NULL; return filelist; } // return real paths of the files char **parse_filelist(char *zipfile, char **file_entries, int nb_entries, bool flatten) { // no need to free since the path lists should live until the end of the // program char **files = static_cast(malloc(sizeof(char *) * nb_entries)); char **zip_paths = file_entries; for (int i = 0; i < nb_entries; i++) { char *p_eq = strchr(file_entries[i], '='); if (p_eq != NULL) { if (flatten) { fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile, "= can't be used with flatten"); free(files); return NULL; } if (p_eq == file_entries[i]) { fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile, "A zip path should be given before ="); free(files); return NULL; } *p_eq = '\0'; files[i] = p_eq + 1; if (files[i][0] == '\0') { files[i] = NULL; } } else { files[i] = file_entries[i]; zip_paths[i] = NULL; } } return files; } // Execute the create operation int create(char *zipfile, char **file_entries, bool flatten, bool verbose, bool compress) { int nb_entries = 0; while (file_entries[nb_entries] != NULL) { nb_entries++; } char **zip_paths = file_entries; char **files = parse_filelist(zipfile, file_entries, nb_entries, flatten); if (files == NULL) { return -1; } u8 size = ZipBuilder::EstimateSize(files, zip_paths, nb_entries); if (size == 0) { return -1; } std::unique_ptr builder(ZipBuilder::Create(zipfile, size)); if (builder.get() == NULL) { fprintf(stderr, "Unable to create zip file %s: %s.\n", zipfile, strerror(errno)); return -1; } for (int i = 0; i < nb_entries; i++) { if (add_file(builder, files[i], zip_paths[i], flatten, verbose, compress) < 0) { return -1; } } if (builder->Finish() < 0) { fprintf(stderr, "%s\n", builder->GetError()); return -1; } return 0; } } // namespace devtools_ijar // // main method // static void usage(char *progname) { fprintf(stderr, "Usage: %s [vxc[fC]] x.zip [-d exdir] [[zip_path1=]file1 ... " "[zip_pathn=]filen]\n", progname); fprintf(stderr, " v verbose - list all file in x.zip\n"); fprintf(stderr, " x extract - extract files in x.zip to current directory, or " " an optional directory relative to the current directory " " specified through -d option\n"); fprintf(stderr, " c create - add files to x.zip\n"); fprintf(stderr, " f flatten - flatten files to use with create or " "extract operation\n"); fprintf(stderr, " C compress - compress files when using the create operation\n"); fprintf(stderr, "x and c cannot be used in the same command-line.\n"); fprintf(stderr, "\nFor every file, a path in the zip can be specified. Examples:\n"); fprintf(stderr, " zipper c x.zip a/b/__init__.py= # Add an empty file at " "a/b/__init__.py\n"); fprintf(stderr, " zipper c x.zip a/b/main.py=foo/bar/bin.py # Add file " "foo/bar/bin.py at a/b/main.py\n"); fprintf(stderr, "\nIf the zip path is not specified, it is assumed to be the file " "path.\n"); exit(1); } int main(int argc, char **argv) { bool extract = false; bool verbose = false; bool create = false; bool compress = false; bool flatten = false; if (argc < 3) { usage(argv[0]); } for (int i = 0; argv[1][i] != 0; i++) { switch (argv[1][i]) { case 'x': extract = true; break; case 'v': verbose = true; break; case 'c': create = true; break; case 'f': flatten = true; break; case 'C': compress = true; break; default: usage(argv[0]); } } // x and c cannot be used in the same command-line. if (create && extract) { usage(argv[0]); } // Calculate the argument index of the first entry file. int filelist_start_index; if (argc > 3 && strcmp(argv[3], "-d") == 0) { filelist_start_index = 5; } else { filelist_start_index = 3; } char** filelist = NULL; // We have one option file. Read and extract the content. if (argc == filelist_start_index + 1 && argv[filelist_start_index][0] == '@') { char* filelist_name = argv[filelist_start_index]; filelist = devtools_ijar::read_filelist(filelist_name + 1); if (filelist == NULL) { fprintf(stderr, "Can't read file list %s: %s.\n", filelist_name, strerror(errno)); return -1; } // We have more than one files. Assume that they are all file entries. } else if (argc >= filelist_start_index + 1) { filelist = argv + filelist_start_index; } else { // There are no entry files specified. This is forbidden if we are creating // a zip file. if (create) { fprintf(stderr, "Can't create zip without input files specified."); return -1; } } if (create) { // Create a zip return devtools_ijar::create(argv[2], filelist, flatten, verbose, compress); } else { char* exdir = NULL; if (argc > 3 && strcmp(argv[3], "-d") == 0) { exdir = argv[4]; } // Extraction / list mode return devtools_ijar::extract(argv[2], exdir, filelist, verbose, extract, flatten); } }