Remove changing uids/timestamps from zip/jar files
Pass -X to zip so that Unix UID/GID and extra timestamps aren't
saved into the zip files.
Add a new tool, ziptime, that uses a very stripped down copy of
zipalign. It no longer depends on libandroidfw, and now rewrites the
timestamps in place instead of making a copy of the zipfile. This should
improve speed and reduce disk requirements, especially with the large
packaging zip files.
Bug: 24201956
Change-Id: I50f68669f659da1b4393e964ad40b6aafb00c1e7
diff --git a/tools/ziptime/Android.mk b/tools/ziptime/Android.mk
new file mode 100644
index 0000000..3575229
--- /dev/null
+++ b/tools/ziptime/Android.mk
@@ -0,0 +1,32 @@
+#
+# Copyright 2015 The Android Open Source Project
+#
+# 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 timestamp removal tool
+#
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES := \
+ ZipTime.cpp \
+ ZipEntry.cpp \
+ ZipFile.cpp
+
+LOCAL_MODULE := ziptime
+LOCAL_MODULE_HOST_OS := darwin linux windows
+
+include $(BUILD_HOST_EXECUTABLE)
diff --git a/tools/ziptime/README.txt b/tools/ziptime/README.txt
new file mode 100644
index 0000000..8a101e9
--- /dev/null
+++ b/tools/ziptime/README.txt
@@ -0,0 +1,10 @@
+ziptime -- zip timestamp tool
+
+usage: ziptime file.zip
+
+ file.zip is an existing Zip archive to rewrite
+
+
+This tools replaces the timestamps in the zip headers with a static time
+(Jan 1 2008). The extra fields are not changed, so you'll need to use the
+-X option to zip so that it doesn't create the 'universal time' extra.
diff --git a/tools/ziptime/ZipEntry.cpp b/tools/ziptime/ZipEntry.cpp
new file mode 100644
index 0000000..bdbdd32
--- /dev/null
+++ b/tools/ziptime/ZipEntry.cpp
@@ -0,0 +1,156 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//
+// Access to entries in a Zip archive.
+//
+
+#include "ZipEntry.h"
+
+#include <stdio.h>
+#include <string.h>
+#include <assert.h>
+
+using namespace android;
+
+#define LOG(...) fprintf(stderr, __VA_ARGS__)
+
+/* Jan 01 2008 */
+#define STATIC_DATE (28 << 9 | 1 << 5 | 1)
+#define STATIC_TIME 0
+
+/*
+ * Initialize a new ZipEntry structure from a FILE* positioned at a
+ * CentralDirectoryEntry. Rewrites the headers to remove the dynamic
+ * timestamps.
+ *
+ * On exit, the file pointer will be at the start of the next CDE or
+ * at the EOCD.
+ */
+status_t ZipEntry::initAndRewriteFromCDE(FILE* fp)
+{
+ status_t result;
+ long posn;
+
+ /* read the CDE */
+ result = mCDE.rewrite(fp);
+ if (result != 0) {
+ LOG("mCDE.rewrite failed\n");
+ return result;
+ }
+
+ /* using the info in the CDE, go load up the LFH */
+ posn = ftell(fp);
+ if (fseek(fp, mCDE.mLocalHeaderRelOffset, SEEK_SET) != 0) {
+ LOG("local header seek failed (%ld)\n",
+ mCDE.mLocalHeaderRelOffset);
+ return -1;
+ }
+
+ result = mLFH.rewrite(fp);
+ if (result != 0) {
+ LOG("mLFH.rewrite failed\n");
+ return result;
+ }
+
+ if (fseek(fp, posn, SEEK_SET) != 0)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * ===========================================================================
+ * ZipEntry::LocalFileHeader
+ * ===========================================================================
+ */
+
+/*
+ * Rewrite a local file header.
+ *
+ * On entry, "fp" points to the signature at the start of the header.
+ */
+status_t ZipEntry::LocalFileHeader::rewrite(FILE* fp)
+{
+ status_t result = 0;
+ unsigned char buf[kLFHLen];
+
+ if (fread(buf, 1, kLFHLen, fp) != kLFHLen)
+ return -1;
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ LOG("whoops: didn't find expected signature\n");
+ return -1;
+ }
+
+ ZipEntry::putShortLE(&buf[0x0a], STATIC_TIME);
+ ZipEntry::putShortLE(&buf[0x0c], STATIC_DATE);
+
+ if (fseek(fp, -kLFHLen, SEEK_CUR) != 0)
+ return -1;
+
+ if (fwrite(buf, 1, kLFHLen, fp) != kLFHLen)
+ return -1;
+
+ return 0;
+}
+
+/*
+ * ===========================================================================
+ * ZipEntry::CentralDirEntry
+ * ===========================================================================
+ */
+
+/*
+ * Read and rewrite the central dir entry that appears next in the file.
+ *
+ * On entry, "fp" should be positioned on the signature bytes for the
+ * entry. On exit, "fp" will point at the signature word for the next
+ * entry or for the EOCD.
+ */
+status_t ZipEntry::CentralDirEntry::rewrite(FILE* fp)
+{
+ status_t result = 0;
+ unsigned char buf[kCDELen];
+ unsigned short fileNameLength, extraFieldLength, fileCommentLength;
+
+ if (fread(buf, 1, kCDELen, fp) != kCDELen)
+ return -1;
+
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature) {
+ LOG("Whoops: didn't find expected signature\n");
+ return -1;
+ }
+
+ ZipEntry::putShortLE(&buf[0x0c], STATIC_TIME);
+ ZipEntry::putShortLE(&buf[0x0e], STATIC_DATE);
+
+ fileNameLength = ZipEntry::getShortLE(&buf[0x1c]);
+ extraFieldLength = ZipEntry::getShortLE(&buf[0x1e]);
+ fileCommentLength = ZipEntry::getShortLE(&buf[0x20]);
+ mLocalHeaderRelOffset = ZipEntry::getLongLE(&buf[0x2a]);
+
+ if (fseek(fp, -kCDELen, SEEK_CUR) != 0)
+ return -1;
+
+ if (fwrite(buf, 1, kCDELen, fp) != kCDELen)
+ return -1;
+
+ if (fseek(fp, fileNameLength + extraFieldLength + fileCommentLength, SEEK_CUR) != 0)
+ return -1;
+
+ return 0;
+}
diff --git a/tools/ziptime/ZipEntry.h b/tools/ziptime/ZipEntry.h
new file mode 100644
index 0000000..beea20c
--- /dev/null
+++ b/tools/ziptime/ZipEntry.h
@@ -0,0 +1,116 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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 archive entries.
+//
+// The ZipEntry class is tightly meshed with the ZipFile class.
+//
+#ifndef __LIBS_ZIPENTRY_H
+#define __LIBS_ZIPENTRY_H
+
+#include <stdlib.h>
+#include <stdio.h>
+
+typedef int status_t;
+
+namespace android {
+
+class ZipFile;
+
+/*
+ * ZipEntry objects represent a single entry in a Zip archive.
+ *
+ * File information is stored in two places: next to the file data (the Local
+ * File Header, and possibly a Data Descriptor), and at the end of the file
+ * (the Central Directory Entry). The two must be kept in sync.
+ */
+class ZipEntry {
+public:
+ friend class ZipFile;
+
+ ZipEntry(void) {}
+ ~ZipEntry(void) {}
+
+ /*
+ * Some basic functions for raw data manipulation. "LE" means
+ * Little Endian.
+ */
+ static inline unsigned short getShortLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8);
+ }
+ static inline unsigned long getLongLE(const unsigned char* buf) {
+ return buf[0] | (buf[1] << 8) | (buf[2] << 16) | (buf[3] << 24);
+ }
+ static inline void putShortLE(unsigned char* buf, short val) {
+ buf[0] = (unsigned char) val;
+ buf[1] = (unsigned char) (val >> 8);
+ }
+
+protected:
+ /*
+ * Initialize the structure from the file, which is pointing at
+ * our Central Directory entry. And rewrite it.
+ */
+ status_t initAndRewriteFromCDE(FILE* fp);
+
+private:
+ /* these are private and not defined */
+ ZipEntry(const ZipEntry& src);
+ ZipEntry& operator=(const ZipEntry& src);
+
+ /*
+ * Every entry in the Zip archive starts off with one of these.
+ */
+ class LocalFileHeader {
+ public:
+ LocalFileHeader(void) {}
+
+ status_t rewrite(FILE* fp);
+
+ enum {
+ kSignature = 0x04034b50,
+ kLFHLen = 30, // LocalFileHdr len, excl. var fields
+ };
+ };
+
+ /*
+ * Every entry in the Zip archive has one of these in the "central
+ * directory" at the end of the file.
+ */
+ class CentralDirEntry {
+ public:
+ CentralDirEntry(void) :
+ mLocalHeaderRelOffset(0)
+ {}
+
+ status_t rewrite(FILE* fp);
+
+ unsigned long mLocalHeaderRelOffset;
+
+ enum {
+ kSignature = 0x02014b50,
+ kCDELen = 46, // CentralDirEnt len, excl. var fields
+ };
+ };
+
+ LocalFileHeader mLFH;
+ CentralDirEntry mCDE;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPENTRY_H
diff --git a/tools/ziptime/ZipFile.cpp b/tools/ziptime/ZipFile.cpp
new file mode 100644
index 0000000..c4c898e
--- /dev/null
+++ b/tools/ziptime/ZipFile.cpp
@@ -0,0 +1,240 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//
+// Access to Zip archives.
+//
+
+#include "ZipFile.h"
+
+#include <memory.h>
+#include <sys/stat.h>
+#include <errno.h>
+#include <assert.h>
+
+using namespace android;
+
+#define LOG(...) fprintf(stderr, __VA_ARGS__)
+
+/*
+ * Open a file and rewrite the headers
+ */
+status_t ZipFile::rewrite(const char* zipFileName)
+{
+ assert(mZipFp == NULL); // no reopen
+
+ /* open the file */
+ mZipFp = fopen(zipFileName, "r+b");
+ if (mZipFp == NULL) {
+ int err = errno;
+ LOG("fopen failed: %d\n", err);
+ return -1;
+ }
+
+ /*
+ * Load the central directory. If that fails, then this probably
+ * isn't a Zip archive.
+ */
+ return rewriteCentralDir();
+}
+
+/*
+ * Find the central directory, read and rewrite the contents.
+ *
+ * The fun thing about ZIP archives is that they may or may not be
+ * readable from start to end. In some cases, notably for archives
+ * that were written to stdout, the only length information is in the
+ * central directory at the end of the file.
+ *
+ * Of course, the central directory can be followed by a variable-length
+ * comment field, so we have to scan through it backwards. The comment
+ * is at most 64K, plus we have 18 bytes for the end-of-central-dir stuff
+ * itself, plus apparently sometimes people throw random junk on the end
+ * just for the fun of it.
+ *
+ * This is all a little wobbly. If the wrong value ends up in the EOCD
+ * area, we're hosed. This appears to be the way that everbody handles
+ * it though, so we're in pretty good company if this fails.
+ */
+status_t ZipFile::rewriteCentralDir(void)
+{
+ status_t result = 0;
+ unsigned char* buf = NULL;
+ off_t fileLength, seekStart;
+ long readAmount;
+ int i;
+
+ fseek(mZipFp, 0, SEEK_END);
+ fileLength = ftell(mZipFp);
+ rewind(mZipFp);
+
+ /* too small to be a ZIP archive? */
+ if (fileLength < EndOfCentralDir::kEOCDLen) {
+ LOG("Length is %ld -- too small\n", (long)fileLength);
+ result = -1;
+ goto bail;
+ }
+
+ buf = new unsigned char[EndOfCentralDir::kMaxEOCDSearch];
+ if (buf == NULL) {
+ LOG("Failure allocating %d bytes for EOCD search",
+ EndOfCentralDir::kMaxEOCDSearch);
+ result = -1;
+ goto bail;
+ }
+
+ if (fileLength > EndOfCentralDir::kMaxEOCDSearch) {
+ seekStart = fileLength - EndOfCentralDir::kMaxEOCDSearch;
+ readAmount = EndOfCentralDir::kMaxEOCDSearch;
+ } else {
+ seekStart = 0;
+ readAmount = (long) fileLength;
+ }
+ if (fseek(mZipFp, seekStart, SEEK_SET) != 0) {
+ LOG("Failure seeking to end of zip at %ld", (long) seekStart);
+ result = -1;
+ goto bail;
+ }
+
+ /* read the last part of the file into the buffer */
+ if (fread(buf, 1, readAmount, mZipFp) != (size_t) readAmount) {
+ LOG("short file? wanted %ld\n", readAmount);
+ result = -1;
+ goto bail;
+ }
+
+ /* find the end-of-central-dir magic */
+ for (i = readAmount - 4; i >= 0; i--) {
+ if (buf[i] == 0x50 &&
+ ZipEntry::getLongLE(&buf[i]) == EndOfCentralDir::kSignature)
+ {
+ break;
+ }
+ }
+ if (i < 0) {
+ LOG("EOCD not found, not Zip\n");
+ result = -1;
+ goto bail;
+ }
+
+ /* extract eocd values */
+ result = mEOCD.readBuf(buf + i, readAmount - i);
+ if (result != 0) {
+ LOG("Failure reading %ld bytes of EOCD values", readAmount - i);
+ goto bail;
+ }
+
+ /*
+ * So far so good. "mCentralDirSize" is the size in bytes of the
+ * central directory, so we can just seek back that far to find it.
+ * We can also seek forward mCentralDirOffset bytes from the
+ * start of the file.
+ *
+ * We're not guaranteed to have the rest of the central dir in the
+ * buffer, nor are we guaranteed that the central dir will have any
+ * sort of convenient size. We need to skip to the start of it and
+ * read the header, then the other goodies.
+ *
+ * The only thing we really need right now is the file comment, which
+ * we're hoping to preserve.
+ */
+ if (fseek(mZipFp, mEOCD.mCentralDirOffset, SEEK_SET) != 0) {
+ LOG("Failure seeking to central dir offset %ld\n",
+ mEOCD.mCentralDirOffset);
+ result = -1;
+ goto bail;
+ }
+
+ /*
+ * Loop through and read the central dir entries.
+ */
+ int entry;
+ for (entry = 0; entry < mEOCD.mTotalNumEntries; entry++) {
+ ZipEntry* pEntry = new ZipEntry;
+
+ result = pEntry->initAndRewriteFromCDE(mZipFp);
+ if (result != 0) {
+ LOG("initFromCDE failed\n");
+ delete pEntry;
+ goto bail;
+ }
+
+ delete pEntry;
+ }
+
+
+ /*
+ * If all went well, we should now be back at the EOCD.
+ */
+ unsigned char checkBuf[4];
+ if (fread(checkBuf, 1, 4, mZipFp) != 4) {
+ LOG("EOCD check read failed\n");
+ result = -1;
+ goto bail;
+ }
+ if (ZipEntry::getLongLE(checkBuf) != EndOfCentralDir::kSignature) {
+ LOG("EOCD read check failed\n");
+ result = -1;
+ goto bail;
+ }
+
+bail:
+ delete[] buf;
+ return result;
+}
+
+/*
+ * ===========================================================================
+ * ZipFile::EndOfCentralDir
+ * ===========================================================================
+ */
+
+/*
+ * Read the end-of-central-dir fields.
+ *
+ * "buf" should be positioned at the EOCD signature, and should contain
+ * the entire EOCD area including the comment.
+ */
+status_t ZipFile::EndOfCentralDir::readBuf(const unsigned char* buf, int len)
+{
+ unsigned short diskNumber, diskWithCentralDir, numEntries;
+
+ if (len < kEOCDLen) {
+ /* looks like ZIP file got truncated */
+ LOG(" Zip EOCD: expected >= %d bytes, found %d\n",
+ kEOCDLen, len);
+ return -1;
+ }
+
+ /* this should probably be an assert() */
+ if (ZipEntry::getLongLE(&buf[0x00]) != kSignature)
+ return -1;
+
+ diskNumber = ZipEntry::getShortLE(&buf[0x04]);
+ diskWithCentralDir = ZipEntry::getShortLE(&buf[0x06]);
+ numEntries = ZipEntry::getShortLE(&buf[0x08]);
+ mTotalNumEntries = ZipEntry::getShortLE(&buf[0x0a]);
+ mCentralDirOffset = ZipEntry::getLongLE(&buf[0x10]);
+
+ if (diskNumber != 0 || diskWithCentralDir != 0 ||
+ numEntries != mTotalNumEntries)
+ {
+ LOG("Archive spanning not supported\n");
+ return -1;
+ }
+
+ return 0;
+}
diff --git a/tools/ziptime/ZipFile.h b/tools/ziptime/ZipFile.h
new file mode 100644
index 0000000..50ca923
--- /dev/null
+++ b/tools/ziptime/ZipFile.h
@@ -0,0 +1,84 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * 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.
+ */
+
+//
+// Class to rewrite zip file headers to remove dynamic timestamps.
+//
+#ifndef __LIBS_ZIPFILE_H
+#define __LIBS_ZIPFILE_H
+
+#include <stdio.h>
+
+#include "ZipEntry.h"
+
+namespace android {
+
+/*
+ * Manipulate a Zip archive.
+ */
+class ZipFile {
+public:
+ ZipFile(void) : mZipFp(NULL) {}
+ ~ZipFile(void) {
+ if (mZipFp != NULL)
+ fclose(mZipFp);
+ }
+
+ /*
+ * Rewrite an archive's headers to remove dynamic timestamps.
+ */
+ status_t rewrite(const char* zipFileName);
+
+private:
+ /* these are private and not defined */
+ ZipFile(const ZipFile& src);
+ ZipFile& operator=(const ZipFile& src);
+
+ class EndOfCentralDir {
+ public:
+ EndOfCentralDir(void) : mTotalNumEntries(0), mCentralDirOffset(0) {}
+
+ status_t readBuf(const unsigned char* buf, int len);
+
+ unsigned short mTotalNumEntries;
+ unsigned long mCentralDirOffset; // offset from first disk
+
+ enum {
+ kSignature = 0x06054b50,
+ kEOCDLen = 22, // EndOfCentralDir len, excl. comment
+
+ kMaxCommentLen = 65535, // longest possible in ushort
+ kMaxEOCDSearch = kMaxCommentLen + EndOfCentralDir::kEOCDLen,
+
+ };
+ };
+
+ /* read all entries in the central dir */
+ status_t rewriteCentralDir(void);
+
+ /*
+ * We use stdio FILE*, which gives us buffering but makes dealing
+ * with files >2GB awkward. Until we support Zip64, we're fine.
+ */
+ FILE* mZipFp; // Zip file pointer
+
+ /* one of these per file */
+ EndOfCentralDir mEOCD;
+};
+
+}; // namespace android
+
+#endif // __LIBS_ZIPFILE_H
diff --git a/tools/ziptime/ZipTime.cpp b/tools/ziptime/ZipTime.cpp
new file mode 100644
index 0000000..99d3231
--- /dev/null
+++ b/tools/ziptime/ZipTime.cpp
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * 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 tool to remove dynamic timestamps
+ */
+#include "ZipFile.h"
+
+#include <stdlib.h>
+#include <stdio.h>
+
+using namespace android;
+
+static void usage(void)
+{
+ fprintf(stderr, "Zip timestamp utility\n");
+ fprintf(stderr, "Copyright (C) 2015 The Android Open Source Project\n\n");
+ fprintf(stderr, "Usage: ziptime file.zip\n");
+}
+
+int main(int argc, char* const argv[])
+{
+ if (argc != 2) {
+ usage();
+ return 2;
+ }
+
+ ZipFile zip;
+ if (zip.rewrite(argv[1]) != 0) {
+ fprintf(stderr, "Unable to rewrite '%s' as zip archive\n", argv[1]);
+ return 1;
+ }
+
+ return 0;
+}