aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar Damien Martin-Guillerez <dmarting@google.com>2015-05-28 11:12:31 +0000
committerGravatar Laszlo Csomor <laszlocsomor@google.com>2015-05-28 14:33:37 +0000
commit084411250036cb5724a6cfe8c65744b679cb436e (patch)
tree87a79459c1eadca68093d6b9d6f022d7c21886ce
parent7ad2f099a7c4a6095833b616e5ee2a19f3874546 (diff)
Refactor ZIP implementation of ijar
It extracts Zip/Unzip methods of ijar in a separate library. A zipper binary is provided to test that implementation outside. Note that this implementation does not compute CRC-32 and unzip will complain on file zipped with it (but Java won't complain). The error handling has been replaced to use proper error reporting instead of launching abort()'s allover the place so ijar's zip library can be used outside of ijar. Finally, support for ZIP preamble has been added to handle self-extractable ZIP files. -- Change-Id: I833034b4c0054925bada75102fe040db875da789 Reviewed-on: https://bazel-review.googlesource.com/#/c/1371/ MOS_MIGRATED_REVID=94656262
-rw-r--r--third_party/ijar/BUILD28
-rw-r--r--third_party/ijar/common.h16
-rw-r--r--third_party/ijar/ijar.cc113
-rw-r--r--third_party/ijar/test/BUILD17
-rwxr-xr-xthird_party/ijar/test/zip_test.sh80
-rw-r--r--third_party/ijar/zip.cc916
-rw-r--r--third_party/ijar/zip.h163
-rw-r--r--third_party/ijar/zip_main.cc299
8 files changed, 1226 insertions, 406 deletions
diff --git a/third_party/ijar/BUILD b/third_party/ijar/BUILD
index 882f79fbde..1ab0975c02 100644
--- a/third_party/ijar/BUILD
+++ b/third_party/ijar/BUILD
@@ -7,12 +7,28 @@ package(
licenses(["notice"]) # Apache 2.0
-cc_binary(
- name = "ijar",
- srcs = glob([
- "*.cc",
- "*.h",
- ]),
+cc_library(
+ name = "zip",
+ srcs = ["zip.cc"],
+ hdrs = [
+ "common.h",
+ "zip.h",
+ ],
# TODO(bazel-team): we should replace the -lz flag.
linkopts = ["-lz"],
)
+
+cc_binary(
+ name = "zipper",
+ srcs = ["zip_main.cc"],
+ deps = [":zip"],
+)
+
+cc_binary(
+ name = "ijar",
+ srcs = [
+ "classfile.cc",
+ "ijar.cc",
+ ],
+ deps = [":zip"],
+)
diff --git a/third_party/ijar/common.h b/third_party/ijar/common.h
index d383bfd4f2..118041b852 100644
--- a/third_party/ijar/common.h
+++ b/third_party/ijar/common.h
@@ -95,24 +95,8 @@ inline void put_n(u1 *&p, const u1 *src, size_t n) {
p += n;
}
-
-// Opens "file_in" (a .jar file) for reading, and writes an interface
-// .jar to "file_out". Returns zero on success.
-int OpenFilesAndProcessJar(const char *file_out, const char *file_in);
-
-
-// Reads a JVM class from classdata_in (of the specified length), and
-// writes out a simplified class to classdata_out, advancing the
-// pointer.
-void StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length);
-
extern bool verbose;
-// Given the data in the zip file, returns the offset of the central
-// directory and the number of files contained in it in *offset and
-// *entries, respectively. Returns true on success, or false on error.
-bool FindZipCentralDirectory(const u1* bytes, size_t in_length, u4* offset);
-
} // namespace devtools_ijar
#endif // INCLUDED_DEVTOOLS_IJAR_COMMON_H
diff --git a/third_party/ijar/ijar.cc b/third_party/ijar/ijar.cc
index ccf1018382..860b551f66 100644
--- a/third_party/ijar/ijar.cc
+++ b/third_party/ijar/ijar.cc
@@ -21,9 +21,117 @@
#include <string.h>
#include <stdlib.h>
#include <limits.h>
+#include <errno.h>
+#include <memory>
-#include "third_party/ijar/common.h"
+#include "third_party/ijar/zip.h"
+namespace devtools_ijar {
+
+bool verbose = false;
+
+// Reads a JVM class from classdata_in (of the specified length), and
+// writes out a simplified class to classdata_out, advancing the
+// pointer.
+void StripClass(u1 *&classdata_out, const u1 *classdata_in, size_t in_length);
+
+const char* CLASS_EXTENSION = ".class";
+const size_t CLASS_EXTENSION_LENGTH = strlen(CLASS_EXTENSION);
+
+// ZipExtractorProcessor that select only .class file and use
+// StripClass to generate an interface class, storing as a new file
+// in the specified ZipBuilder.
+class JarStripperProcessor : public ZipExtractorProcessor {
+ public:
+ JarStripperProcessor() {}
+ virtual ~JarStripperProcessor() {}
+
+ 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);
+
+ private:
+ // Not owned by JarStripperProcessor, see SetZipBuilder().
+ ZipBuilder* builder;
+
+ public:
+ // Set the ZipBuilder to add the ijar class to the output zip file.
+ // This pointer should not be deleted while this class is still in use and
+ // it should be set before any call to the Process() method.
+ void SetZipBuilder(ZipBuilder* builder) {
+ this->builder = builder;
+ }
+};
+
+bool JarStripperProcessor::Accept(const char* filename, const u4 attr) {
+ ssize_t offset = strlen(filename) - CLASS_EXTENSION_LENGTH;
+ if (offset >= 0) {
+ return strcmp(filename + offset, CLASS_EXTENSION) == 0;
+ }
+ return false;
+}
+
+void JarStripperProcessor::Process(const char* filename, const u4 attr,
+ const u1* data, const size_t size) {
+ if (verbose) {
+ fprintf(stderr, "INFO: StripClass: %s\n", filename);
+ }
+ u1 *q = builder->NewFile(filename, 0);
+ u1 *classdata_out = q;
+ StripClass(q, data, size); // actually process it
+ size_t out_length = q - classdata_out;
+ builder->FinishFile(out_length);
+}
+
+// Opens "file_in" (a .jar file) for reading, and writes an interface
+// .jar to "file_out".
+void OpenFilesAndProcessJar(const char *file_out, const char *file_in) {
+ JarStripperProcessor processor;
+ std::unique_ptr<ZipExtractor> in(ZipExtractor::Create(file_in, &processor));
+ if (in.get() == NULL) {
+ fprintf(stderr, "Unable to open Zip file %s: %s\n", file_in,
+ strerror(errno));
+ abort();
+ }
+ u8 output_length = in->CalculateOutputLength();
+ std::unique_ptr<ZipBuilder> out(ZipBuilder::Create(file_out, output_length));
+ if (out.get() == NULL) {
+ fprintf(stderr, "Unable to open output file %s: %s\n", file_out,
+ strerror(errno));
+ abort();
+ }
+ processor.SetZipBuilder(out.get());
+
+ // Process all files in the zip
+ if (in->ProcessAll() < 0) {
+ fprintf(stderr, "%s\n", in->GetError());
+ abort();
+ }
+
+ // Add dummy file, since javac doesn't like truly empty jars.
+ if (out->GetNumberFiles() == 0) {
+ out->WriteEmptyFile("dummy");
+ }
+ // Finish writing the output file
+ if (out->Finish() < 0) {
+ fprintf(stderr, "%s\n", out->GetError());
+ abort();
+ }
+ // Get all file size
+ size_t in_length = in->GetSize();
+ size_t out_length = out->GetSize();
+ if (verbose) {
+ fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n",
+ file_in, file_out,
+ static_cast<int>(100.0 * out_length / in_length));
+ }
+}
+
+} // namespace devtools_ijar
+
+//
+// main method
+//
static void usage() {
fprintf(stderr, "Usage: ijar [-v] x.jar [x_interface.jar>]\n");
fprintf(stderr, "Creates an interface jar from the specified jar file.\n");
@@ -69,5 +177,6 @@ int main(int argc, char **argv) {
fprintf(stderr, "INFO: writing to '%s'.\n", filename_out);
}
- return devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in);
+ devtools_ijar::OpenFilesAndProcessJar(filename_out, filename_in);
+ return 0;
}
diff --git a/third_party/ijar/test/BUILD b/third_party/ijar/test/BUILD
index b46d742313..d1c32e1cad 100644
--- a/third_party/ijar/test/BUILD
+++ b/third_party/ijar/test/BUILD
@@ -41,6 +41,23 @@ sh_test(
shard_count = 5,
)
+sh_test(
+ name = "zip_test",
+ size = "large",
+ srcs = ["zip_test.sh"],
+ args = [
+ "$(location //third_party/ijar:zipper)",
+ # We assume unzip and zip to be on the path
+ "unzip",
+ "zip",
+ ],
+ data = [
+ "testenv.sh",
+ "//src/test/shell:bashunit",
+ "//third_party/ijar:zipper",
+ ],
+)
+
java_library(
name = "invokedynamic",
testonly = 1,
diff --git a/third_party/ijar/test/zip_test.sh b/third_party/ijar/test/zip_test.sh
new file mode 100755
index 0000000000..ed51174288
--- /dev/null
+++ b/third_party/ijar/test/zip_test.sh
@@ -0,0 +1,80 @@
+#!/bin/bash -eu
+#
+# Copyright 2015 Google Inc. 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
+
+# Integration tests for ijar zipper/unzipper
+
+
+DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
+
+## Inputs
+ZIPPER=$TEST_SRCDIR/$1
+shift
+UNZIP=$1
+shift
+ZIP=$1
+shift
+
+## Test framework
+source ${DIR}/testenv.sh || { echo "testenv.sh not found!" >&2; exit 1; }
+
+# Assertion
+function assert_unzip_same_as_zipper() {
+ local folder1=$(mktemp -d ${TEST_TMPDIR}/output.XXXXXXXX)
+ local folder2=$(mktemp -d ${TEST_TMPDIR}/output.XXXXXXXX)
+ (cd $folder1 && $UNZIP -q $1 || true) # ignore CRC32 errors
+ (cd $folder2 && $ZIPPER x $1)
+ diff -r $folder1 $folder2 &> $TEST_log \
+ || fail "Unzip and Zipper resulted in different output"
+}
+
+function assert_zipper_same_after_unzip() {
+ local zipfile=${TEST_TMPDIR}/output.zip
+ (cd $1 && $ZIPPER c ${zipfile} $(find . | sed 's|^./||' | grep -v '^.$'))
+ local folder=$(mktemp -d ${TEST_TMPDIR}/output.XXXXXXXX)
+ (cd $folder && $UNZIP -q ${zipfile} || true) # ignore CRC32 errors
+ diff -r $1 $folder &> $TEST_log \
+ || fail "Unzip after zipper output differ"
+}
+
+#### Tests
+
+function test_zipper() {
+ mkdir -p ${TEST_TMPDIR}/test/path/to/some
+ mkdir -p ${TEST_TMPDIR}/test/some/other/path
+ echo "toto" > ${TEST_TMPDIR}/test/path/to/some/file
+ echo "titi" > ${TEST_TMPDIR}/test/path/to/some/other_file
+ chmod +x ${TEST_TMPDIR}/test/path/to/some/other_file
+ echo "tata" > ${TEST_TMPDIR}/test/file
+ assert_zipper_same_after_unzip ${TEST_TMPDIR}/test
+ assert_unzip_same_as_zipper ${TEST_TMPDIR}/output.zip
+
+ # Test flatten option
+ (cd ${TEST_TMPDIR}/test && $ZIPPER cf ${TEST_TMPDIR}/output.zip \
+ $(find . | sed 's|^./||' | grep -v '^.$'))
+ $ZIPPER v ${TEST_TMPDIR}/output.zip >$TEST_log
+ expect_log "file"
+ expect_log "other_file"
+ expect_not_log "path"
+
+ # Test adding leading garbage at the begining of the file (for
+ # self-extractable binary).
+ echo "abcdefghi" >${TEST_TMPDIR}/test.zip
+ cat ${TEST_TMPDIR}/output.zip >>${TEST_TMPDIR}/test.zip
+ $ZIPPER v ${TEST_TMPDIR}/test.zip >$TEST_log
+ expect_log "file"
+ expect_log "other_file"
+ expect_not_log "path"
+}
+
+run_suite "zipper tests"
diff --git a/third_party/ijar/zip.cc b/third_party/ijar/zip.cc
index 5c29c139e1..adc80b6299 100644
--- a/third_party/ijar/zip.cc
+++ b/third_party/ijar/zip.cc
@@ -28,6 +28,7 @@
#include <fcntl.h>
#include <stddef.h>
#include <stdint.h>
+#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
@@ -36,7 +37,7 @@
#include <limits>
#include <vector>
-#include "third_party/ijar/common.h"
+#include "third_party/ijar/zip.h"
#include <zlib.h>
#define LOCAL_FILE_HEADER_SIGNATURE 0x04034b50
@@ -55,18 +56,8 @@
#define GENERAL_PURPOSE_BIT_FLAG_SUPPORTED \
(GENERAL_PURPOSE_BIT_FLAG_COMPRESSED | GENERAL_PURPOSE_BIT_FLAG_UTF8_ENCODED)
-#define STRINGIFY(x) #x
-#define SYSCALL(expr) do { \
- if ((expr) < 0) { \
- perror(STRINGIFY(expr)); \
- abort(); \
- } \
- } while (0)
-
namespace devtools_ijar {
-bool verbose = false;
-
// In the absence of ZIP64 support, zip files are limited to 4GB.
// http://www.info-zip.org/FAQ.html#limits
static const u8 kMaximumOutputSize = std::numeric_limits<uint32_t>::max();
@@ -74,72 +65,62 @@ static const u8 kMaximumOutputSize = std::numeric_limits<uint32_t>::max();
static bool ProcessCentralDirEntry(const u1 *&p,
size_t *compressed_size,
size_t *uncompressed_size,
- bool *is_class_file);
-
-struct JarStripper {
- JarStripper(const u1 * const zipdata_in,
- u1 * const zipdata_out,
- size_t in_length,
- const u1 * central_dir) :
- zipdata_in_(zipdata_in),
- zipdata_out_(zipdata_out),
- zipdata_in_mapped_(zipdata_in),
- zipdata_out_mapped_(zipdata_out),
- central_dir_(central_dir),
- in_length_(in_length),
- p(zipdata_in),
- q(zipdata_out),
- central_dir_current_(central_dir) {
- uncompressed_data_allocated_ = INITIAL_BUFFER_SIZE;
- uncompressed_data_ =
- reinterpret_cast<u1*>(malloc(uncompressed_data_allocated_));
+ char *filename,
+ size_t filename_size,
+ u4 *attr,
+ u4 *offset);
+
+//
+// A class representing a ZipFile for reading. Its public API is exposed
+// using the ZipExtractor abstract class.
+//
+class InputZipFile : public ZipExtractor {
+ public:
+ InputZipFile(ZipExtractorProcessor *processor, int fd, off_t in_length,
+ off_t in_offset, const u1* zipdata_in, const u1* central_dir);
+ virtual ~InputZipFile();
+
+ virtual const char* GetError() {
+ if (errmsg[0] == 0) {
+ return NULL;
+ }
+ return errmsg;
}
- ~JarStripper() {
- free(uncompressed_data_);
+ virtual bool ProcessNext();
+ virtual void Reset();
+ virtual size_t GetSize() {
+ return in_length_;
}
- // Scan through the input .jar file, stripping each class file and
- // emitting it to the output .jar file. Returns the size of the
- // output.
- off_t Run();
+ virtual u8 CalculateOutputLength();
private:
- struct LocalFileEntry {
- // Start of the local header (in the output buffer).
- size_t local_header_offset;
- size_t uncompressed_length;
+ ZipExtractorProcessor *processor;
- // Start/length of the file_name in the local header.
- u1 *file_name;
- u2 file_name_length;
-
- // Start/length of the extra_field in the local header.
- const u1 *extra_field;
- u2 extra_field_length;
- };
-
- // Buffer size is initially INITIAL_BUFFER_SIZE. It doubles in size every
- // time it is found too small, until it reaches MAX_BUFFER_SIZE. If that is
- // not enough, we bail out. We only decompress class files, so they should
- // be smaller than 64K anyway, but we give a little leeway.
- static const size_t INITIAL_BUFFER_SIZE = 256 * 1024; // 256K
- static const size_t MAX_BUFFER_SIZE = 16 * 1024 * 1024;
- static const size_t MAX_MAPPED_REGION = 32 * 1024 * 1024;
+ int fd_in; // Input file descripor
+ // InputZipFile is responsible for maintaining the following
+ // pointers. They are allocated by the Create() method before
+ // the object is actually created using mmap.
const u1 * const zipdata_in_; // start of input file mmap
- u1 * const zipdata_out_; // start of output file mmap
const u1 * zipdata_in_mapped_; // start of still mapped region
- u1 * zipdata_out_mapped_; // start of still mapped region
const u1 * const central_dir_; // central directory in input file
size_t in_length_; // size of the input file
+ size_t in_offset_; // offset the input file
const u1 *p; // input cursor
- u1 *q; // output cursor
+
const u1* central_dir_current_; // central dir input cursor
- std::vector<LocalFileEntry*> entries_;
+ // Buffer size is initially INITIAL_BUFFER_SIZE. It doubles in size every
+ // time it is found too small, until it reaches MAX_BUFFER_SIZE. If that is
+ // not enough, we bail out. We only decompress class files, so they should
+ // be smaller than 64K anyway, but we give a little leeway.
+ static const size_t INITIAL_BUFFER_SIZE = 256 * 1024; // 256K
+ static const size_t MAX_BUFFER_SIZE = 16 * 1024 * 1024;
+ static const size_t MAX_MAPPED_REGION = 32 * 1024 * 1024;
// These metadata fields are the fields of the ZIP header of the file being
// processed.
@@ -160,147 +141,204 @@ struct JarStripper {
u1 *uncompressed_data_;
size_t uncompressed_data_allocated_;
- // Read one entry from input zip file, and emit corresponding entry
- // in output zip file.
- void ProcessLocalFileEntry(size_t compressed_size, size_t uncompressed_size,
- bool is_class_file);
+ // Copy of the last filename entry - Null-terminated.
+ char filename[PATH_MAX];
+ // The external file attribute field
+ u4 attr;
- // Add a zero-byte file called "dummy" to the output zip file.
- void AddDummyEntry();
+ // last error
+ char errmsg[4*PATH_MAX];
- // Write the ZIP central directory structure for each local file
- // entry in "entries".
- void WriteCentralDirectory();
+ int error(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(errmsg, 4*PATH_MAX, fmt, ap);
+ va_end(ap);
+ return -1;
+ }
// Check that at least n bytes remain in the input file, otherwise
// abort with an error message. "state" is the name of the field
// we're about to read, for diagnostics.
- void EnsureRemaining(size_t n, const char *state) {
+ int EnsureRemaining(size_t n, const char *state) {
size_t in_offset = p - zipdata_in_;
size_t remaining = in_length_ - in_offset;
if (n > remaining) {
- fprintf(stderr, "Premature end of file (at offset %zd, state=%s); "
- "expected %zd more bytes but found %zd.\n",
- in_offset, state, n, remaining);
- abort();
+ return error("Premature end of file (at offset %zd, state=%s); "
+ "expected %zd more bytes but found %zd.\n",
+ in_offset, state, n, remaining);
}
+ return 0;
}
+ // Read one entry from input zip file
+ int ProcessLocalFileEntry(size_t compressed_size, size_t uncompressed_size);
+
+ // Uncompress a file from the archive using zlib. The pointer returned
+ // is owned by InputZipFile, so it must not be freed. Advances the input
+ // cursor to the first byte after the compressed data.
+ u1* UncompressFile();
+
+ // Skip a file
+ int SkipFile(const bool compressed);
+
+ // Process a file
+ int ProcessFile(const bool compressed);
+};
+
+//
+// A class implementing ZipBuilder that represent an open zip file for writing.
+//
+class OutputZipFile : public ZipBuilder {
+ public:
+ OutputZipFile(int fd, u1 * const zipdata_out) :
+ fd_out(fd),
+ zipdata_out_(zipdata_out),
+ zipdata_out_mapped_(zipdata_out),
+ q(zipdata_out) {
+ errmsg[0] = 0;
+ }
+
+ virtual const char* GetError() {
+ if (errmsg[0] == 0) {
+ return NULL;
+ }
+ return errmsg;
+ }
+
+ virtual ~OutputZipFile() { Finish(); }
+ virtual u1* NewFile(const char* filename, const u4 attr);
+ virtual int FinishFile(size_t filelength);
+ virtual int WriteEmptyFile(const char* filename);
+ virtual size_t GetSize() {
+ return Offset(q);
+ }
+ virtual int GetNumberFiles() {
+ return entries_.size();
+ }
+ virtual int Finish();
+
+ private:
+ struct LocalFileEntry {
+ // Start of the local header (in the output buffer).
+ size_t local_header_offset;
+ size_t uncompressed_length;
+
+ // external attributes field
+ u4 external_attr;
+
+ // Start/length of the file_name in the local header.
+ u1 *file_name;
+ u2 file_name_length;
+
+ // Start/length of the extra_field in the local header.
+ const u1 *extra_field;
+ u2 extra_field_length;
+ };
+
+ int fd_out; // file descriptor for the output file
+
+ // OutputZipFile is responsible for maintaining the following
+ // pointers. They are allocated by the Create() method before
+ // the object is actually created using mmap.
+ u1 * const zipdata_out_; // start of output file mmap
+ u1 * zipdata_out_mapped_; // start of still mapped region
+ u1 *q; // output cursor
+
+ u1 *compressed_size_ptr; // Current pointer to "compressed size" entry.
+
+ // List of entries to write the central directory
+ std::vector<LocalFileEntry*> entries_;
+
+ // last error
+ char errmsg[4*PATH_MAX];
+
+ int error(const char *fmt, ...) {
+ va_list ap;
+ va_start(ap, fmt);
+ vsnprintf(errmsg, 4*PATH_MAX, fmt, ap);
+ va_end(ap);
+ return -1;
+ }
+
+ // Write the ZIP central directory structure for each local file
+ // entry in "entries".
+ void WriteCentralDirectory();
+
// Returns the offset of the pointer relative to the start of the
// output zip file.
size_t Offset(const u1 *const x) {
return x - zipdata_out_;
}
- // Uncompress a file from the archive using zlib. The pointer returned
- // is owned by JarStripper, so it must not be freed. Advances the input cursor
- // to the first byte after the compressed data.
- u1* UncompressFile();
-
// Write ZIP file header in the output. Since the compressed size is not
// known in advance, it must be recorded later. This method returns a pointer
// to "compressed size" in the file header that should be passed to
// WriteFileSizeInLocalFileHeader() later.
- u1* WriteLocalFileHeader();
+ u1* WriteLocalFileHeader(const char *filename, const u4 attr);
// Fill in the "compressed size" and "uncompressed size" fields in a local
// file header previously written by WriteLocalFileHeader().
void WriteFileSizeInLocalFileHeader(u1 *compressed_size_ptr,
size_t out_length);
+};
- // Process raw class data. Expects that metadata fields are filled out, i.e.
- // extract_version_, general_purpose_bit_flag and their kin.
- void ProcessRawClassData(const u1 *classdata_in);
-
- // Process a compressed file as a class
- void ProcessCompressedFile();
-
- // Skip a compressed file
- void SkipCompressedFile();
-
- // Process an uncompressed file
- void ProcessUncompressedFile();
- // Skip an uncompressed file
- void SkipUncompressedFile();
-};
-off_t JarStripper::Run() {
- // Process all the entries in the central directory. Also make sure that the
+//
+// Implementation of InputZipFile
+//
+bool InputZipFile::ProcessNext() {
+ // Process the next entry in the central directory. Also make sure that the
// content pointer is in sync.
- for (int i = 0; true; i++) {
- size_t compressed, uncompressed;
- bool is_class_file;
- if (!ProcessCentralDirEntry(central_dir_current_,
- &compressed, &uncompressed, &is_class_file)) {
- break;
- }
-
- EnsureRemaining(4, "signature");
- u4 signature = get_u4le(p);
- if (signature == LOCAL_FILE_HEADER_SIGNATURE) {
- ProcessLocalFileEntry(compressed, uncompressed, is_class_file);
- } else {
- fprintf(stderr,
- "local file header signature for file %d not found\n", i);
- abort();
- }
+ size_t compressed, uncompressed;
+ u4 offset;
+ if (!ProcessCentralDirEntry(central_dir_current_, &compressed, &uncompressed,
+ filename, PATH_MAX, &attr, &offset)) {
+ return false;
}
- // Add dummy file, since javac doesn't like truly empty jars.
- if (entries_.empty()) AddDummyEntry();
-
- WriteCentralDirectory();
- return Offset(q); // = output length
-}
-
-void JarStripper::AddDummyEntry() {
- const u1* file_name = (const u1*) "dummy";
- size_t file_name_length = strlen("dummy");
-
- LocalFileEntry *entry = new LocalFileEntry;
- entry->local_header_offset = Offset(q);
+ // There might be an offset specified in the central directory that does
+ // not match the file offset, if so, correct the pointer.
+ if (offset != 0 && (p != (zipdata_in_ + in_offset_ + offset))) {
+ p = zipdata_in_ + offset;
+ }
- // Output the ZIP local_file_header:
- put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
- put_u2le(q, 10); // extract_version
- put_u2le(q, 0); // general_purpose_bit_flag
- put_u2le(q, 0); // compression_method
- put_u2le(q, 0); // last_mod_file_time
- put_u2le(q, 0); // last_mod_file_date
- put_u4le(q, 0); // crc32
- put_u4le(q, 0); // compressed_size
- put_u4le(q, 0); // uncompressed_size
- put_u2le(q, file_name_length);
- put_u2le(q, 0); // extra_field_length
- put_n(q, file_name, file_name_length);
+ if (EnsureRemaining(4, "signature") < 0) {
+ return false;
+ }
+ u4 signature = get_u4le(p);
+ if (signature == LOCAL_FILE_HEADER_SIGNATURE) {
+ if (ProcessLocalFileEntry(compressed, uncompressed) < 0) {
+ return false;
+ }
+ } else {
+ error("local file header signature for file %s not found\n", filename);
+ return false;
+ }
- entry->file_name_length = file_name_length;
- entry->extra_field_length = 0;
- entry->extra_field = (const u1*) "";
- entry->file_name = (u1*) strdup((const char *) file_name);
- entries_.push_back(entry);
+ return true;
}
-void JarStripper::ProcessLocalFileEntry(
- size_t compressed_size, size_t uncompressed_size, bool is_class_file) {
- EnsureRemaining(26, "extract_version");
+int InputZipFile::ProcessLocalFileEntry(
+ size_t compressed_size, size_t uncompressed_size) {
+ if (EnsureRemaining(26, "extract_version") < 0) {
+ return -1;
+ }
extract_version_ = get_u2le(p);
general_purpose_bit_flag_ = get_u2le(p);
if ((general_purpose_bit_flag_ & ~GENERAL_PURPOSE_BIT_FLAG_SUPPORTED) != 0) {
- fprintf(stderr, "Unsupported value (0x%04x) in general purpose bit flag.\n",
- general_purpose_bit_flag_);
- abort();
+ return error("Unsupported value (0x%04x) in general purpose bit flag.\n",
+ general_purpose_bit_flag_);
}
compression_method_ = get_u2le(p);
if (compression_method_ != COMPRESSION_METHOD_DEFLATED &&
compression_method_ != COMPRESSION_METHOD_STORED) {
- fprintf(stderr, "Unsupported compression method (%d).\n",
- compression_method_);
- abort();
+ return error("Unsupported compression method (%d).\n",
+ compression_method_);
}
// skip over: last_mod_file_time, last_mod_file_date, crc32
@@ -310,11 +348,15 @@ void JarStripper::ProcessLocalFileEntry(
file_name_length_ = get_u2le(p);
extra_field_length_ = get_u2le(p);
- EnsureRemaining(file_name_length_, "file_name");
+ if (EnsureRemaining(file_name_length_, "file_name") < 0) {
+ return -1;
+ }
file_name_ = p;
p += file_name_length_;
- EnsureRemaining(extra_field_length_, "extra_field");
+ if (EnsureRemaining(extra_field_length_, "extra_field") < 0) {
+ return -1;
+ }
extra_field_ = p;
p += extra_field_length_;
@@ -328,8 +370,7 @@ void JarStripper::ProcessLocalFileEntry(
compressed_size_ = compressed_size;
} else {
if (compressed_size_ != compressed_size) {
- fprintf(stderr, "central directory and file header inconsistent\n");
- abort();
+ return error("central directory and file header inconsistent\n");
}
}
@@ -337,22 +378,17 @@ void JarStripper::ProcessLocalFileEntry(
uncompressed_size_ = uncompressed_size;
} else {
if (uncompressed_size_ != uncompressed_size) {
- fprintf(stderr, "central directory and file header inconsistent\n");
- abort();
+ return error("central directory and file header inconsistent\n");
}
}
- if (is_class_file) {
- if (is_compressed) {
- ProcessCompressedFile();
- } else {
- ProcessUncompressedFile();
+ if (processor->Accept(filename, attr)) {
+ if (ProcessFile(is_compressed) < 0) {
+ return -1;
}
} else {
- if (is_compressed) {
- SkipCompressedFile();
- } else {
- SkipUncompressedFile();
+ if (SkipFile(is_compressed) < 0) {
+ return -1;
}
}
@@ -369,31 +405,32 @@ void JarStripper::ProcessLocalFileEntry(
}
}
- if (q - zipdata_out_mapped_ > MAX_MAPPED_REGION) {
- munmap(zipdata_out_mapped_, MAX_MAPPED_REGION);
- zipdata_out_mapped_ += MAX_MAPPED_REGION;
- }
-
if (p - zipdata_in_mapped_ > MAX_MAPPED_REGION) {
munmap(const_cast<u1*>(zipdata_in_mapped_), MAX_MAPPED_REGION);
zipdata_in_mapped_ += MAX_MAPPED_REGION;
}
+
+ return 0;
}
-void JarStripper::SkipUncompressedFile() {
- // In this case, compressed_size_ == uncompressed_size_ (since the file is
- // uncompressed), so we can use either.
- if (compressed_size_ != uncompressed_size_) {
- fprintf(stderr, "compressed size != uncompressed size, although the file "
- "is uncompressed.\n");
- abort();
+int InputZipFile::SkipFile(const bool compressed) {
+ if (!compressed) {
+ // In this case, compressed_size_ == uncompressed_size_ (since the file is
+ // uncompressed), so we can use either.
+ if (compressed_size_ != uncompressed_size_) {
+ return error("compressed size != uncompressed size, although the file "
+ "is uncompressed.\n");
+ }
}
- EnsureRemaining(compressed_size_, "file_data");
+ if (EnsureRemaining(compressed_size_, "file_data") < 0) {
+ return -1;
+ }
p += compressed_size_;
+ return 0;
}
-u1* JarStripper::UncompressFile() {
+u1* InputZipFile::UncompressFile() {
size_t in_offset = p - zipdata_in_;
size_t remaining = in_length_ - in_offset;
z_stream stream;
@@ -406,8 +443,8 @@ u1* JarStripper::UncompressFile() {
int ret = inflateInit2(&stream, -MAX_WBITS);
if (ret != Z_OK) {
- fprintf(stderr, "inflateInit: %d\n", ret);
- abort();
+ error("inflateInit: %d\n", ret);
+ return NULL;
}
int uncompressed_until_now = 0;
@@ -438,11 +475,10 @@ u1* JarStripper::UncompressFile() {
// the decompressed data. Enlarge that buffer and try again.
if (uncompressed_data_allocated_ == MAX_BUFFER_SIZE) {
- fprintf(stderr,
- "ijar does not support decompressing files "
- "larger than %dMB.\n",
- (int) (MAX_BUFFER_SIZE/(1024*1024)));
- abort();
+ error("ijar does not support decompressing files "
+ "larger than %dMB.\n",
+ (int) (MAX_BUFFER_SIZE/(1024*1024)));
+ return NULL;
}
uncompressed_data_allocated_ *= 2;
@@ -460,132 +496,38 @@ u1* JarStripper::UncompressFile() {
case Z_STREAM_ERROR:
case Z_NEED_DICT:
default: {
- fprintf(stderr, "zlib returned error code %d during inflate.\n", ret);
- abort();
+ error("zlib returned error code %d during inflate.\n", ret);
+ return NULL;
}
}
}
}
-void JarStripper::SkipCompressedFile() {
- EnsureRemaining(compressed_size_, "file_data");
- p += compressed_size_;
-}
-
-u1* JarStripper::WriteLocalFileHeader() {
- LocalFileEntry *entry = new LocalFileEntry;
- entry->local_header_offset = Offset(q);
- entry->file_name_length = file_name_length_;
- entry->file_name = new u1[file_name_length_];
- memcpy(entry->file_name, file_name_, file_name_length_);
- entry->extra_field_length = 0;
- entry->extra_field = (const u1*)"";
-
- // Output the ZIP local_file_header:
- put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
- put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
- put_u2le(q, 0); // general purpose bit flag
- put_u2le(q, COMPRESSION_METHOD_STORED); // compression method:
- put_u2le(q, 0); // last_mod_file_time
- put_u2le(q, 0); // last_mod_file_date
- put_u4le(q, 0); // crc32 (jar/javac tools don't care)
- u1 *compressed_size_ptr = q;
- put_u4le(q, 0); // compressed_size = placeholder
- put_u4le(q, 0); // uncompressed_size = placeholder
- put_u2le(q, entry->file_name_length);
- put_u2le(q, entry->extra_field_length);
-
- put_n(q, entry->file_name, entry->file_name_length);
- put_n(q, entry->extra_field, entry->extra_field_length);
- entries_.push_back(entry);
-
- return compressed_size_ptr;
-}
-
-void JarStripper::WriteFileSizeInLocalFileHeader(u1 *compressed_size_ptr,
- size_t out_length) {
- // uncompressed size and compressed size are the same, since the output
- // ijar is uncompressed.
- put_u4le(compressed_size_ptr, out_length); // compressed_size
- put_u4le(compressed_size_ptr, out_length); // uncompressed_size
-}
-
-void JarStripper::ProcessRawClassData(const u1 *classdata_in) {
- if (verbose) {
- // file_name_ is not NUL-terminated.
- fprintf(stderr, "INFO: StripClass: %.*s\n", file_name_length_, file_name_);
- }
- u1 *compressed_size_ptr = WriteLocalFileHeader();
-
- u1 *classdata_out = q;
- StripClass(q, classdata_in, uncompressed_size_); // actually process it
- size_t out_length = q - classdata_out;
-
- WriteFileSizeInLocalFileHeader(compressed_size_ptr, out_length);
- entries_.back()->uncompressed_length = out_length;
-}
-
-void JarStripper::ProcessCompressedFile() {
- u1 *classdata_in = UncompressFile();
- ProcessRawClassData(classdata_in);
-}
+int InputZipFile::ProcessFile(const bool compressed) {
+ const u1 *file_data;
+ if (compressed) {
+ file_data = UncompressFile();
+ if (file_data == NULL) {
+ return -1;
+ }
+ } else {
+ // In this case, compressed_size_ == uncompressed_size_ (since the file is
+ // uncompressed), so we can use either.
+ if (compressed_size_ != uncompressed_size_) {
+ return error("compressed size != uncompressed size, although the file "
+ "is uncompressed.\n");
+ }
-void JarStripper::ProcessUncompressedFile() {
- // In this case, compressed_size_ == uncompressed_size_ (since the file is
- // uncompressed), so we can use either.
- if (compressed_size_ != uncompressed_size_) {
- fprintf(stderr, "compressed size != uncompressed size, although the file "
- "is uncompressed.\n");
- abort();
+ if (EnsureRemaining(compressed_size_, "file_data") < 0) {
+ return -1;
+ }
+ file_data = p;
+ p += compressed_size_;
}
-
- EnsureRemaining(compressed_size_, "file_data");
- const u1 *file_data = p;
- p += compressed_size_;
- ProcessRawClassData(file_data);
+ processor->Process(filename, attr, file_data, uncompressed_size_);
+ return 0;
}
-void JarStripper::WriteCentralDirectory() {
- // central directory:
- const u1 *central_directory_start = q;
- for (int ii = 0; ii < entries_.size(); ++ii) {
- LocalFileEntry *entry = entries_[ii];
- put_u4le(q, CENTRAL_FILE_HEADER_SIGNATURE);
- put_u2le(q, 0); // version made by
-
- put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
- put_u2le(q, 0); // general purpose bit flag
- put_u2le(q, COMPRESSION_METHOD_STORED); // compression method:
- put_u2le(q, 0); // last_mod_file_time
- put_u2le(q, 0); // last_mod_file_date
- put_u4le(q, 0); // crc32 (jar/javac tools don't care)
- put_u4le(q, entry->uncompressed_length); // compressed_size
- put_u4le(q, entry->uncompressed_length); // uncompressed_size
- put_u2le(q, entry->file_name_length);
- put_u2le(q, entry->extra_field_length);
-
- put_u2le(q, 0); // file comment length
- put_u2le(q, 0); // disk number start
- put_u2le(q, 0); // internal file attributes
- put_u4le(q, 0); // external file attributes
- // relative offset of local header:
- put_u4le(q, entry->local_header_offset);
-
- put_n(q, entry->file_name, entry->file_name_length);
- put_n(q, entry->extra_field, entry->extra_field_length);
- }
- u4 central_directory_size = q - central_directory_start;
-
- put_u4le(q, END_OF_CENTRAL_DIR_SIGNATURE);
- put_u2le(q, 0); // number of this disk
- put_u2le(q, 0); // number of the disk with the start of the central directory
- put_u2le(q, entries_.size()); // # central dir entries on this disk
- put_u2le(q, entries_.size()); // total # entries in the central directory
- put_u4le(q, central_directory_size); // size of the central directory
- put_u4le(q, Offset(central_directory_start)); // offset of start of central
- // directory wrt starting disk
- put_u2le(q, 0); // .ZIP file comment length
-}
// Reads and returns some metadata of the next file from the central directory:
// - compressed size
@@ -599,7 +541,7 @@ void JarStripper::WriteCentralDirectory() {
// that has a signature, so parsing it this way is safe.
static bool ProcessCentralDirEntry(
const u1 *&p, size_t *compressed_size, size_t *uncompressed_size,
- bool *is_class_file) {
+ char *filename, size_t filename_size, u4 *attr, u4 *offset) {
u4 signature = get_u4le(p);
if (signature != CENTRAL_FILE_HEADER_SIGNATURE) {
return false;
@@ -611,11 +553,15 @@ static bool ProcessCentralDirEntry(
u2 file_name_length = get_u2le(p);
u2 extra_field_length = get_u2le(p);
u2 file_comment_length = get_u2le(p);
- p += 12; // skip to file name field
+ p += 4; // skip to external file attributes field
+ *attr = get_u4le(p);
+ *offset = get_u4le(p);
{
- static const int len = strlen(".class");
- *is_class_file = file_name_length >= len &&
- memcmp(".class", p + file_name_length - len, len) == 0;
+ size_t len = (file_name_length < filename_size)
+ ? file_name_length
+ : (filename_size - 1);
+ memcpy(reinterpret_cast<void*>(filename), p, len);
+ filename[len] = 0;
}
p += file_name_length;
p += extra_field_length;
@@ -623,9 +569,46 @@ static bool ProcessCentralDirEntry(
return true;
}
+// Gives a maximum bound on the size of the interface JAR. Basically, adds
+// the difference between the compressed and uncompressed sizes to the size
+// of the input file.
+u8 InputZipFile::CalculateOutputLength() {
+ const u1* current = central_dir_;
+
+ u8 compressed_size = 0;
+ u8 uncompressed_size = 0;
+ u8 skipped_compressed_size = 0;
+ u4 attr;
+ u4 offset;
+ char filename[PATH_MAX];
+
+ while (true) {
+ size_t file_compressed, file_uncompressed;
+ if (!ProcessCentralDirEntry(current,
+ &file_compressed, &file_uncompressed,
+ filename, PATH_MAX, &attr, &offset)) {
+ break;
+ }
+
+ if (processor->Accept(filename, attr)) {
+ compressed_size += (u8) file_compressed;
+ uncompressed_size += (u8) file_uncompressed;
+ } else {
+ skipped_compressed_size += file_compressed;
+ }
+ }
+
+ // The worst case is when the output is simply the input uncompressed. The
+ // metadata in the zip file will stay the same, so the file will grow by the
+ // difference between the compressed and uncompressed sizes.
+ return (u8) in_length_ - skipped_compressed_size
+ + (uncompressed_size - compressed_size);
+}
+
// Given the data in the zip file, returns the offset of the central directory
// and the number of files contained in it.
-bool FindZipCentralDirectory(const u1* bytes, size_t in_length, u4* offset) {
+bool FindZipCentralDirectory(const u1* bytes, size_t in_length,
+ u4* offset, const u1** central_dir) {
static const int MAX_COMMENT_LENGTH = 0xffff;
static const int CENTRAL_DIR_LOCATOR_SIZE = 22;
// Maximum distance of start of central dir locator from end of file
@@ -664,13 +647,16 @@ bool FindZipCentralDirectory(const u1* bytes, size_t in_length, u4* offset) {
return false;
}
+ const u1* end_of_central_dir = current;
get_u4le(current); // central directory locator signature, already checked
u2 number_of_this_disk = get_u2le(current);
u2 disk_with_central_dir = get_u2le(current);
u2 central_dir_entries_on_this_disk = get_u2le(current);
u2 central_dir_entries = get_u2le(current);
- get_u4le(current); // central directory size
+ u4 central_dir_size = get_u4le(current);
u4 central_dir_offset = get_u4le(current);
+ u2 file_comment_length = get_u2le(current);
+ current += file_comment_length; // set current to the end of the central dir
if (number_of_this_disk != 0
|| disk_with_central_dir != 0
@@ -681,115 +667,281 @@ bool FindZipCentralDirectory(const u1* bytes, size_t in_length, u4* offset) {
// Do not change output values before determining that they are OK.
*offset = central_dir_offset;
+ // Central directory start can then be used to determine the actual
+ // starts of the zip file (which can be different in case of a non-zip
+ // header like for auto-extractable binaries).
+ *central_dir = end_of_central_dir - central_dir_size;
return true;
}
-// Gives a maximum bound on the size of the interface JAR. Basically, adds
-// the difference between the compressed and uncompressed sizes to the size
-// of the input file.
-static u8 CalculateOutputLength(const u1* central_dir, size_t in_length) {
- const u1* current = central_dir;
-
- u8 compressed_size = 0;
- u8 uncompressed_size = 0;
- u8 skipped_compressed_size = 0;
-
- while (true) {
- size_t file_compressed, file_uncompressed;
- bool is_class_file;
- if (!ProcessCentralDirEntry(current,
- &file_compressed, &file_uncompressed,
- &is_class_file)) {
- break;
- }
+void InputZipFile::Reset() {
+ central_dir_current_ = central_dir_;
+ zipdata_in_mapped_ = zipdata_in_;
+ p = zipdata_in_ + in_offset_;
+}
- if (is_class_file) {
- compressed_size += (u8) file_compressed;
- uncompressed_size += (u8) file_uncompressed;
- } else {
- skipped_compressed_size += file_compressed;
- }
+int ZipExtractor::ProcessAll() {
+ while (ProcessNext()) {}
+ if (GetError() != NULL) {
+ return -1;
}
-
- // The worst case is when the output is simply the input uncompressed. The
- // metadata in the zip file will stay the same, so the file will grow by the
- // difference between the compressed and uncompressed sizes.
- return (u8) in_length - skipped_compressed_size
- + (uncompressed_size - compressed_size);
+ return 0;
}
-int OpenFilesAndProcessJar(const char *file_out, const char *file_in) {
- int fd_in = open(file_in, O_RDONLY);
+ZipExtractor* ZipExtractor::Create(const char* filename,
+ ZipExtractorProcessor *processor) {
+ int fd_in = open(filename, O_RDONLY);
if (fd_in < 0) {
- fprintf(stderr, "Can't open file %s for reading: %s.\n",
- file_in, strerror(errno));
- return 1;
+ return NULL;
}
- off_t length;
- SYSCALL(length = lseek(fd_in, 0, SEEK_END));
+ off_t length = lseek(fd_in, 0, SEEK_END);
+ if (length < 0) {
+ return NULL;
+ }
void *zipdata_in = mmap(NULL, length, PROT_READ, MAP_PRIVATE, fd_in, 0);
if (zipdata_in == MAP_FAILED) {
- perror("mmap(in)");
- return 1;
+ return NULL;
}
u4 central_dir_offset;
+ const u1 *central_dir = NULL;
if (!devtools_ijar::FindZipCentralDirectory(
- static_cast<const u1*>(zipdata_in), length, &central_dir_offset)) {
- abort();
+ static_cast<const u1*>(zipdata_in), length,
+ &central_dir_offset, &central_dir)) {
+ errno = EIO; // we don't really have a good error number
+ return NULL;
+ }
+ const u1 *zipdata_start = static_cast<const u1*>(zipdata_in);
+ off_t offset = - static_cast<off_t>(zipdata_start
+ + central_dir_offset
+ - central_dir);
+
+ return new InputZipFile(processor, fd_in, length, offset,
+ zipdata_start, central_dir);
+}
+
+InputZipFile::InputZipFile(ZipExtractorProcessor *processor, int fd,
+ off_t in_length, off_t in_offset,
+ const u1* zipdata_in, const u1* central_dir)
+ : processor(processor), fd_in(fd),
+ zipdata_in_(zipdata_in), zipdata_in_mapped_(zipdata_in),
+ central_dir_(central_dir), in_length_(in_length), in_offset_(in_offset),
+ p(zipdata_in + in_offset), central_dir_current_(central_dir) {
+ uncompressed_data_allocated_ = INITIAL_BUFFER_SIZE;
+ uncompressed_data_ =
+ reinterpret_cast<u1*>(malloc(uncompressed_data_allocated_));
+ errmsg[0] = 0;
+}
+
+InputZipFile::~InputZipFile() {
+ free(uncompressed_data_);
+ close(fd_in);
+}
+
+
+//
+// Implementation of OutputZipFile
+//
+int OutputZipFile::WriteEmptyFile(const char *filename) {
+ const u1* file_name = (const u1*) filename;
+ size_t file_name_length = strlen(filename);
+
+ LocalFileEntry *entry = new LocalFileEntry;
+ entry->local_header_offset = Offset(q);
+ entry->external_attr = 0;
+
+ // Output the ZIP local_file_header:
+ put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
+ put_u2le(q, 10); // extract_version
+ put_u2le(q, 0); // general_purpose_bit_flag
+ put_u2le(q, 0); // compression_method
+ put_u2le(q, 0); // last_mod_file_time
+ put_u2le(q, 0); // last_mod_file_date
+ put_u4le(q, 0); // crc32
+ put_u4le(q, 0); // compressed_size
+ put_u4le(q, 0); // uncompressed_size
+ put_u2le(q, file_name_length);
+ put_u2le(q, 0); // extra_field_length
+ put_n(q, file_name, file_name_length);
+
+ entry->file_name_length = file_name_length;
+ entry->extra_field_length = 0;
+ entry->extra_field = (const u1*) "";
+ entry->file_name = (u1*) strdup((const char *) file_name);
+ entries_.push_back(entry);
+
+ return 0;
+}
+
+void OutputZipFile::WriteCentralDirectory() {
+ // central directory:
+ const u1 *central_directory_start = q;
+ for (int ii = 0; ii < entries_.size(); ++ii) {
+ LocalFileEntry *entry = entries_[ii];
+ put_u4le(q, CENTRAL_FILE_HEADER_SIGNATURE);
+ put_u2le(q, 0); // version made by
+
+ put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
+ put_u2le(q, 0); // general purpose bit flag
+ put_u2le(q, COMPRESSION_METHOD_STORED); // compression method:
+ put_u2le(q, 0); // last_mod_file_time
+ put_u2le(q, 0); // last_mod_file_date
+ put_u4le(q, 0); // crc32 (jar/javac tools don't care)
+ put_u4le(q, entry->uncompressed_length); // compressed_size
+ put_u4le(q, entry->uncompressed_length); // uncompressed_size
+ put_u2le(q, entry->file_name_length);
+ put_u2le(q, entry->extra_field_length);
+
+ put_u2le(q, 0); // file comment length
+ put_u2le(q, 0); // disk number start
+ put_u2le(q, 0); // internal file attributes
+ put_u4le(q, entry->external_attr); // external file attributes
+ // relative offset of local header:
+ put_u4le(q, entry->local_header_offset);
+
+ put_n(q, entry->file_name, entry->file_name_length);
+ put_n(q, entry->extra_field, entry->extra_field_length);
+ }
+ u4 central_directory_size = q - central_directory_start;
+
+ put_u4le(q, END_OF_CENTRAL_DIR_SIGNATURE);
+ put_u2le(q, 0); // number of this disk
+ put_u2le(q, 0); // number of the disk with the start of the central directory
+ put_u2le(q, entries_.size()); // # central dir entries on this disk
+ put_u2le(q, entries_.size()); // total # entries in the central directory
+ put_u4le(q, central_directory_size); // size of the central directory
+ put_u4le(q, Offset(central_directory_start)); // offset of start of central
+ // directory wrt starting disk
+ put_u2le(q, 0); // .ZIP file comment length
+}
+
+u1* OutputZipFile::WriteLocalFileHeader(const char* filename, const u4 attr) {
+ off_t file_name_length_ = strlen(filename);
+ LocalFileEntry *entry = new LocalFileEntry;
+ entry->local_header_offset = Offset(q);
+ entry->file_name_length = file_name_length_;
+ entry->file_name = new u1[file_name_length_];
+ entry->external_attr = attr;
+ memcpy(entry->file_name, filename, file_name_length_);
+ entry->extra_field_length = 0;
+ entry->extra_field = (const u1*)"";
+
+ // Output the ZIP local_file_header:
+ put_u4le(q, LOCAL_FILE_HEADER_SIGNATURE);
+ put_u2le(q, ZIP_VERSION_TO_EXTRACT); // version to extract
+ put_u2le(q, 0); // general purpose bit flag
+ put_u2le(q, COMPRESSION_METHOD_STORED); // compression method:
+ put_u2le(q, 0); // last_mod_file_time
+ put_u2le(q, 0); // last_mod_file_date
+ put_u4le(q, 0); // crc32 (jar/javac tools don't care)
+ u1 *compressed_size_ptr = q;
+ put_u4le(q, 0); // compressed_size = placeholder
+ put_u4le(q, 0); // uncompressed_size = placeholder
+ put_u2le(q, entry->file_name_length);
+ put_u2le(q, entry->extra_field_length);
+
+ put_n(q, entry->file_name, entry->file_name_length);
+ put_n(q, entry->extra_field, entry->extra_field_length);
+ entries_.push_back(entry);
+
+ return compressed_size_ptr;
+}
+
+void OutputZipFile::WriteFileSizeInLocalFileHeader(u1 *compressed_size_ptr,
+ size_t out_length) {
+ // uncompressed size and compressed size are the same, since the output
+ // ijar is uncompressed.
+ put_u4le(compressed_size_ptr, out_length); // compressed_size
+ put_u4le(compressed_size_ptr, out_length); // uncompressed_size
+}
+
+int OutputZipFile::Finish() {
+ if (fd_out > 0) {
+ WriteCentralDirectory();
+ if (ftruncate(fd_out, GetSize()) < 0) {
+ return error("ftruncate(fd_out, GetSize()): %s", strerror(errno));
+ }
+ if (close(fd_out) < 0) {
+ return error("close(fd_out): %s", strerror(errno));
+ }
+ fd_out = -1;
}
+ return 0;
+}
- const u1* central_dir =
- static_cast<const u1*>(zipdata_in) + central_dir_offset;
+u1* OutputZipFile::NewFile(const char* filename, const u4 attr) {
+ compressed_size_ptr = WriteLocalFileHeader(filename, attr);
+ return q;
+}
- u8 output_length = CalculateOutputLength(central_dir, length);
- if (output_length > kMaximumOutputSize) {
+int OutputZipFile::FinishFile(size_t filelength) {
+ WriteFileSizeInLocalFileHeader(compressed_size_ptr, filelength);
+ entries_.back()->uncompressed_length = filelength;
+ q += filelength;
+ return 0;
+}
+
+ZipBuilder* ZipBuilder::Create(const char* zip_file, u8 estimated_size) {
+ if (estimated_size > kMaximumOutputSize) {
fprintf(stderr,
"Uncompressed input jar has size %llu, "
"which exceeds the maximum supported output size %llu.\n"
"Assuming that ijar will be smaller and hoping for the best.\n",
- output_length, kMaximumOutputSize);
- output_length = kMaximumOutputSize;
+ estimated_size, kMaximumOutputSize);
+ estimated_size = kMaximumOutputSize;
}
- int fd_out = open(file_out, O_CREAT|O_RDWR|O_TRUNC, 0644);
+ int fd_out = open(zip_file, O_CREAT|O_RDWR|O_TRUNC, 0644);
if (fd_out < 0) {
- fprintf(stderr, "Can't create file %s: %s.\n",
- file_out, strerror(errno));
- return 1;
+ return NULL;
}
+
// Create mmap-able sparse file
- SYSCALL(ftruncate(fd_out, output_length));
+ if (ftruncate(fd_out, estimated_size) < 0) {
+ return NULL;
+ }
// Ensure that any buffer overflow in JarStripper will result in
// SIGSEGV or SIGBUS by over-allocating beyond the end of the file.
- size_t mmap_length = std::min(output_length + sysconf(_SC_PAGESIZE),
+ size_t mmap_length = std::min(estimated_size + sysconf(_SC_PAGESIZE),
(u8) std::numeric_limits<size_t>::max());
void *zipdata_out = mmap(NULL, mmap_length, PROT_WRITE,
MAP_SHARED, fd_out, 0);
if (zipdata_out == MAP_FAILED) {
- fprintf(stderr, "output_length=%llu\n", output_length);
- perror("mmap(out)");
- return 1;
+ fprintf(stderr, "output_length=%llu\n", estimated_size);
+ return NULL;
}
- JarStripper stripper((const u1*) zipdata_in, (u1*) zipdata_out,
- length, (const u1*) central_dir);
- off_t out_length = stripper.Run();
- SYSCALL(ftruncate(fd_out, out_length));
- SYSCALL(close(fd_out));
- SYSCALL(close(fd_in));
-
- if (verbose) {
- fprintf(stderr, "INFO: produced interface jar: %s -> %s (%d%%).\n",
- file_in, file_out, (int) (100.0 * out_length / length));
- }
+ return new OutputZipFile(fd_out, (u1*) zipdata_out);
+}
- return 0;
+u8 ZipBuilder::EstimateSize(char **files) {
+ struct stat statst;
+ // Digital signature field size = 6, End of central directory = 22, Total = 28
+ u8 size = 28;
+ // Count the size of all the files in the input to estimate the size of the
+ // output.
+ for (int i = 0; files[i] != NULL; i++) {
+ if (stat(files[i], &statst) != 0) {
+ fprintf(stderr, "File %s does not seem to exist.", files[i]);
+ return 0;
+ }
+ size += statst.st_size;
+ // Add sizes of Zip meta data
+ // local file header = 30 bytes
+ // data descriptor = 12 bytes
+ // central directory descriptor = 46 bytes
+ // Total: 88bytes
+ size += 88;
+ // The filename is stored twice (once in the central directory
+ // and once in the local file header).
+ size += strlen(files[i]) * 2;
+ }
+ return size;
}
} // namespace devtools_ijar
diff --git a/third_party/ijar/zip.h b/third_party/ijar/zip.h
new file mode 100644
index 0000000000..97a8e28af2
--- /dev/null
+++ b/third_party/ijar/zip.h
@@ -0,0 +1,163 @@
+// Copyright 2015 Google Inc. 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.h -- .zip (.jar) file reading/writing routines.
+//
+// This file specifies the interface to use the ZIP implementation of ijar.
+//
+
+#ifndef INCLUDED_THIRD_PARTY_IJAR_ZIP_H
+#define INCLUDED_THIRD_PARTY_IJAR_ZIP_H
+
+#include <sys/stat.h>
+
+#include "third_party/ijar/common.h"
+
+namespace devtools_ijar {
+
+// Convert a Unix file mode to a ZIP file attribute
+inline u4 mode_to_zipattr(mode_t m) {
+ return (((u4) m) << 16) + ((m & S_IFDIR) != 0 ? 0x10 : 0);
+}
+
+// Convert a ZIP file attribute to a Unix file mode
+inline mode_t zipattr_to_mode(u4 attr) {
+ return ((mode_t) ((attr >> 16) & 0xffff));
+}
+
+//
+// Class interface for building ZIP files
+//
+class ZipBuilder {
+ public:
+ virtual ~ZipBuilder() {}
+
+ // Returns the text for the last error, or null on no last error.
+ virtual const char* GetError() = 0;
+
+ // Add a new file to the ZIP, the file will have path "filename"
+ // and external attributes "attr". This function returns a pointer
+ // to a memory buffer to write the data of the file into. This buffer
+ // is owned by ZipBuilder and should not be free'd by the caller. The
+ // file length is then specified when the files is finished written
+ // using the FinishFile(size_t) function.
+ // On failure, returns NULL and GetError() will return an non-empty message.
+ virtual u1* NewFile(const char* filename, const u4 attr) = 0;
+
+ // Finish writing a file and specify its length. After calling this method
+ // one should not reuse the pointer given by NewFile.
+ // On failure, returns -1 and GetError() will return an non-empty message.
+ virtual int FinishFile(size_t filelength) = 0;
+
+ // Write an empty file, it is equivalent to:
+ // NewFile(filename, 0);
+ // FinishFile(0);
+ // On failure, returns -1 and GetError() will return an non-empty message.
+ virtual int WriteEmptyFile(const char* filename) = 0;
+
+ // Finish writing the ZIP file. This method can be called only once
+ // (subsequent calls will do nothing) and none of
+ // NewFile/FinishFile/WriteEmptyFile should be called after calling Finish. If
+ // this method was not called when the object is destroyed, it will be called.
+ // It is here as a convenience to get information on the final generated ZIP
+ // file.
+ // On failure, returns -1 and GetError() will return an non-empty message.
+ virtual int Finish() = 0;
+
+ // Get the current size of the ZIP file. This size will not be matching the
+ // final ZIP file until Finish() has been called because Finish() is actually
+ // writing the central directory of the ZIP File.
+ virtual size_t GetSize() = 0;
+
+ // Returns the current number of files stored in the ZIP.
+ virtual int GetNumberFiles() = 0;
+
+ // Create a new ZipBuilder writing the file zip_file and the size of the
+ // output will be at most estimated_size. Use ZipBuilder::EstimateSize() or
+ // ZipExtractor::CalculateOuputLength() to have an estimated_size depending on
+ // a list of file to store.
+ // On failure, returns NULL. Refer to errno for error code.
+ static ZipBuilder* Create(const char* zip_file, u8 estimated_size);
+
+ // Estimate the maximum size of the ZIP files containing files in the "files"
+ // null-terminated array.
+ // Returns 0 on error.
+ static u8 EstimateSize(char **files);
+};
+
+//
+// An abstract class to process data from a ZipExtractor.
+// Derive from this class if you wish to process data from a ZipExtractor.
+//
+class ZipExtractorProcessor {
+ public:
+ virtual ~ZipExtractorProcessor() {}
+
+ // Tells whether to skip or process the file "filename". "attr" is the
+ // external file attributes and can be converted to unix mode using the
+ // zipattr_to_mode() function. This method is suppoed to returns true
+ // if the file should be processed and false if it should be skipped.
+ virtual bool Accept(const char* filename, const u4 attr) = 0;
+
+ // Process a file accepted by Accept. The file "filename" has external
+ // attributes "attr" and length "size". The file content is accessible
+ // in the buffer pointed by "data".
+ virtual void Process(const char* filename, const u4 attr,
+ const u1* data, const size_t size) = 0;
+};
+
+//
+// Class interface for reading ZIP files
+//
+class ZipExtractor {
+ public:
+ virtual ~ZipExtractor() {}
+
+ // Returns the text for the last error, or null on no last error.
+ virtual const char* GetError() = 0;
+
+ // Process the next files, returns false if the end of ZIP file has been
+ // reached. The processor provided by the Create method will be called
+ // if a file is encountered. If false is returned, check the return value
+ // of GetError() for potential errors.
+ virtual bool ProcessNext() = 0;
+
+ // Process the all files, returns -1 on error (GetError() will be populated
+ // on error).
+ virtual int ProcessAll();
+
+ // Reset the file pointer to the beginning.
+ virtual void Reset() = 0;
+
+ // Return the size of the ZIP file.
+ virtual size_t GetSize() = 0;
+
+ // Return the size of the resulting zip file by keeping only file
+ // accepted by the processor and storing them uncompressed. This
+ // method can be used to create a ZipBuilder for storing a subset
+ // of the input files.
+ // On error, 0 is returned and GetError() returns a non-empty message.
+ virtual u8 CalculateOutputLength() = 0;
+
+ // Create a ZipExtractor that extract the zip file "filename" and process
+ // it with "processor".
+ // On error, a null pointer is returned and the value of errno should be
+ // checked.
+ static ZipExtractor* Create(const char* filename,
+ ZipExtractorProcessor *processor);
+};
+
+} // namespace devtools_ijar
+
+#endif // INCLUDED_THIRD_PARTY_IJAR_ZIP_H
diff --git a/third_party/ijar/zip_main.cc b/third_party/ijar/zip_main.cc
new file mode 100644
index 0000000000..8436da40f2
--- /dev/null
+++ b/third_party/ijar/zip_main.cc
@@ -0,0 +1,299 @@
+// 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 "third_party/ijar/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);
+ int len2 = strlen(path2);
+ int 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 (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;
+ }
+ 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) {
+ 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) {
+ 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);
+ }
+ }
+ 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[f]] 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, "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 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;
+ default:
+ usage(argv[0]);
+ }
+ }
+ if (create) {
+ if (extract) {
+ usage(argv[0]);
+ }
+ // Create a zip
+ return devtools_ijar::create(argv[2], argv+3, flatten, verbose);
+ } else {
+ if (flatten) {
+ usage(argv[0]);
+ }
+ // Extraction / list mode
+ return devtools_ijar::extract(argv[2], verbose, extract);
+ }
+}