// Copyright 2015 Google Inc. All rights reserved.
//
// Author: Alan Donovan <adonovan@google.com>
//
// 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 <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <limits.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <errno.h>
#include <memory>
#include "zip.h"
namespace devtools_ijar {
#define SYSCALL(expr) do { \
if ((expr) < 0) { \
perror(#expr); \
abort(); \
} \
} while (0)
//
// A ZipExtractorProcessor that extract all files in the ZIP file.
//
class UnzipProcessor : public ZipExtractorProcessor {
public:
// Create a processor who will extract the files 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, bool verbose, bool extract)
: output_root_(output_root), verbose_(verbose), extract_(extract) {}
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) {
return true;
}
private:
const char *output_root_;
const bool verbose_;
const bool extract_;
};
// 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);
}
}
// Do a recursive mkdir of all folders of path except the last path
// segment (if path ends with a / then the last path segment is empty).
// All folders are created using "mode" for creation mode.
void mkdirs(const char *path, mode_t mode) {
char path_[PATH_MAX];
struct stat statst;
strncpy(path_, path, PATH_MAX);
path_[PATH_MAX-1] = 0;
char *pointer = path_;
while ((pointer = strchr(pointer, '/')) != NULL) {
if (path_ != pointer) { // skip leading slash
*pointer = 0;
if (stat(path_, &statst) != 0) {
if (mkdir(path_, mode) < 0) {
fprintf(stderr, "Cannot create folder %s: %s\n",
path_, strerror(errno));
abort();
}
}
*pointer = '/';
}
pointer++;
}
}
void UnzipProcessor::Process(const char* filename, const u4 attr,
const u1* data, const size_t size) {
mode_t mode = zipattr_to_mode(attr);
mode_t perm = mode & 0777;
bool isdir = (mode & S_IFDIR) != 0;
if (attr == 0) {
// Fallback when the external attribute is not set.
isdir = filename[strlen(filename)-1] == '/';
perm = 0777;
}
if (verbose_) {
printf("%c %o %s\n", isdir ? 'd' : 'f', perm, filename);
}
if (extract_) {
char path[PATH_MAX];
int fd;
concat_path(path, PATH_MAX, output_root_, filename);
mkdirs(path, perm);
if (!isdir) {
fd = open(path, O_CREAT | O_WRONLY, perm);
if (fd < 0) {
fprintf(stderr, "Cannot open file %s for writing: %s\n",
path, strerror(errno));
abort();
}
SYSCALL(write(fd, data, size));
SYSCALL(close(fd));
}
}
}
// 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, bool verbose, bool extract) {
char output_root[PATH_MAX];
getcwd(output_root, PATH_MAX);
UnzipProcessor processor(output_root, verbose, extract);
std::unique_ptr<ZipExtractor> 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;
}
// Execute the create operation
int create(char *zipfile, char **files, bool flatten, bool verbose,
bool compress) {
struct stat statst;
u8 size = ZipBuilder::EstimateSize(files);
if (size == 0) {
return -1;
}
std::unique_ptr<ZipBuilder> 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; files[i] != NULL; i++) {
stat(files[i], &statst);
char path[PATH_MAX];
bool isdir = (statst.st_mode & S_IFDIR) != 0;
if (flatten && isdir) {
continue;
}
// Compute the path, flattening it if requested
if (flatten) {
basename(files[i], path, PATH_MAX);
} else {
strncpy(path, files[i], PATH_MAX);
path[PATH_MAX-1] = 0;
size_t len = strlen(path);
if (isdir && len < PATH_MAX - 1) {
// Add the trailing slash for folders
path[len] = '/';
path[len+1] = 0;
}
}
if (verbose) {
mode_t perm = statst.st_mode & 0777;
printf("%c %o %s\n", isdir ? 'd' : 'f', perm, path);
}
u1 *buffer = builder->NewFile(path, mode_to_zipattr(statst.st_mode));
if (isdir || statst.st_size == 0) {
builder->FinishFile(0);
} else {
// mmap the input file and memcpy
int fd = open(files[i], O_RDONLY);
if (fd < 0) {
fprintf(stderr, "Can't open file %s for reading: %s.\n",
files[i], strerror(errno));
return -1;
}
void *data = mmap(NULL, statst.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
fprintf(stderr, "Can't mmap file %s for reading: %s.\n",
files[i], strerror(errno));
return -1;
}
memcpy(buffer, data, statst.st_size);
munmap(data, statst.st_size);
builder->FinishFile(statst.st_size, compress, true);
}
}
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 [file1...filen]\n", progname);
fprintf(stderr, " v verbose - list all file in x.zip\n");
fprintf(stderr, " x extract - extract file in x.zip in current directory\n");
fprintf(stderr, " c create - add files to x.zip\n");
fprintf(stderr, " f flatten - flatten files to use with create 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");
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]);
}
}
if (create) {
if (extract) {
usage(argv[0]);
}
// Create a zip
return devtools_ijar::create(argv[2], argv + 3, flatten, verbose, compress);
} else {
if (flatten) {
usage(argv[0]);
}
// Extraction / list mode
return devtools_ijar::extract(argv[2], verbose, extract);
}
}