// 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 <errno.h>
#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <memory>
#include <set>
#include <string>
#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<std::string> file_names;
};
// Concatene 2 path, path1 and path2, using / as a directory separator and
// putting the result in "out". "size" specify the size of the output buffer. If
// the result would overflow the output buffer, print an error message and
// return false.
bool 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) {
fprintf(stderr, "paths too long to concat: %s + %s", path1, path2);
return false;
}
strncat(out, path2, size - 1 - l);
return true;
}
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];
if (!concat_path(path, sizeof(path), output_root_, output_file_name) ||
!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 - 1);
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 + 1];
if (exdir != NULL) {
if (!concat_path(output_root, sizeof(output_root), cwd.c_str(), exdir)) {
return -1;
}
} else if (cwd.length() >= sizeof(output_root)) {
fprintf(stderr, "current working directory path too long");
return -1;
} else {
memcpy(output_root, cwd.c_str(), cwd.length() + 1);
}
UnzipProcessor processor(output_root, files, verbose, extract, flatten);
std::unique_ptr<ZipExtractor> extractor(ZipExtractor::Create(zipfile,
&processor));
if (extractor == 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<ZipBuilder> 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<char *>(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 + 1);
// copy the content
char **filelist = static_cast<char **>(result);
char *content = static_cast<char *>(result) + sizeof_array;
memcpy(content, data, file_stat.total_size);
content[file_stat.total_size] = '\0';
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<char **>(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<ZipBuilder> builder(ZipBuilder::Create(zipfile, size));
if (builder == 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);
}
}