[aapt2] Ensure all zip entry times are the same
We want them to be all the same, so that APKs are identical if its
contents are the same, regardless of the time zone of the machine that
produced them. The accompanying change in libziparchive
(Iefc7945bff0610f8a025d4887cd5644cfa3cfb3b) ensures this.
Bug: 277978832
Test: atest aapt2_tests
Change-Id: Iab000b7c74dcb06efad41a6495b73216fd2528d6
diff --git a/tools/aapt2/format/Archive_test.cpp b/tools/aapt2/format/Archive_test.cpp
index 3c44da7..fd50af9 100644
--- a/tools/aapt2/format/Archive_test.cpp
+++ b/tools/aapt2/format/Archive_test.cpp
@@ -14,6 +14,8 @@
* limitations under the License.
*/
+#include <stdlib.h>
+
#include "test/Test.h"
namespace aapt {
@@ -34,6 +36,29 @@
std::string error_;
};
+class TzSetter {
+ public:
+ explicit TzSetter(const std::string& new_tz) {
+ old_tz_ = getenv("TZ");
+ new_tz_ = "TZ=" + new_tz;
+ putenv(const_cast<char*>(new_tz_.c_str()));
+ tzset();
+ }
+
+ ~TzSetter() {
+ if (old_tz_) {
+ putenv(old_tz_);
+ } else {
+ putenv(const_cast<char*>("TZ"));
+ }
+ tzset();
+ }
+
+ private:
+ char* old_tz_;
+ std::string new_tz_;
+};
+
std::unique_ptr<uint8_t[]> MakeTestArray() {
auto array = std::make_unique<uint8_t[]>(kTestDataLength);
for (int index = 0; index < kTestDataLength; ++index) {
@@ -86,6 +111,22 @@
}
}
+void VerifyZipFileTimestamps(const std::string& output_path) {
+ std::unique_ptr<io::ZipFileCollection> zip = io::ZipFileCollection::Create(output_path, nullptr);
+ auto it = zip->Iterator();
+ while (it->HasNext()) {
+ auto file = it->Next();
+ struct tm modification_time;
+ ASSERT_TRUE(file->GetModificationTime(&modification_time));
+ EXPECT_EQ(modification_time.tm_year, 80);
+ EXPECT_EQ(modification_time.tm_mon, 0);
+ EXPECT_EQ(modification_time.tm_mday, 1);
+ EXPECT_EQ(modification_time.tm_hour, 0);
+ EXPECT_EQ(modification_time.tm_min, 0);
+ EXPECT_EQ(modification_time.tm_sec, 0);
+ }
+}
+
TEST_F(ArchiveTest, DirectoryWriteEntrySuccess) {
std::string output_path = GetTestPath("output");
std::unique_ptr<IArchiveWriter> writer = MakeDirectoryWriter(output_path);
@@ -206,4 +247,73 @@
ASSERT_EQ("ZipFileWriteFileError", writer->GetError());
}
+TEST_F(ArchiveTest, ZipFileTimeZoneUTC) {
+ TzSetter tz("UTC0");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
+TEST_F(ArchiveTest, ZipFileTimeZoneWestOfUTC) {
+ TzSetter tz("PST8");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
+TEST_F(ArchiveTest, ZipFileTimeZoneEastOfUTC) {
+ TzSetter tz("EET-2");
+ std::string output_path = GetTestPath("output.apk");
+ std::unique_ptr<IArchiveWriter> writer = MakeZipFileWriter(output_path);
+ std::unique_ptr<uint8_t[]> data1 = MakeTestArray();
+ std::unique_ptr<uint8_t[]> data2 = MakeTestArray();
+
+ ASSERT_TRUE(writer->StartEntry("test1", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data1.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ ASSERT_TRUE(writer->StartEntry("test2", 0));
+ ASSERT_TRUE(writer->Write(static_cast<const void*>(data2.get()), kTestDataLength));
+ ASSERT_TRUE(writer->FinishEntry());
+ ASSERT_FALSE(writer->HadError());
+
+ writer.reset();
+
+ // All zip file entries must have the same timestamp, regardless of time zone. See: b/277978832
+ VerifyZipFileTimestamps(output_path);
+}
+
} // namespace aapt
diff --git a/tools/aapt2/io/File.h b/tools/aapt2/io/File.h
index 08d497d..673d1b7 100644
--- a/tools/aapt2/io/File.h
+++ b/tools/aapt2/io/File.h
@@ -57,6 +57,11 @@
return false;
}
+ // Fills in buf with the last modification time of the file. Returns true if successful,
+ // otherwise false (i.e., the operation is not supported or the file system is unable to provide
+ // a last modification time).
+ virtual bool GetModificationTime(struct tm* buf) const = 0;
+
private:
// Any segments created from this IFile need to be owned by this IFile, so
// keep them
@@ -79,6 +84,10 @@
return file_->GetSource();
}
+ bool GetModificationTime(struct tm* buf) const override {
+ return file_->GetModificationTime(buf);
+ };
+
private:
DISALLOW_COPY_AND_ASSIGN(FileSegment);
diff --git a/tools/aapt2/io/FileSystem.cpp b/tools/aapt2/io/FileSystem.cpp
index a64982a..6a692e4 100644
--- a/tools/aapt2/io/FileSystem.cpp
+++ b/tools/aapt2/io/FileSystem.cpp
@@ -14,9 +14,12 @@
* limitations under the License.
*/
+#define _POSIX_THREAD_SAFE_FUNCTIONS // For mingw localtime_r().
+
#include "io/FileSystem.h"
#include <dirent.h>
+#include <sys/stat.h>
#include "android-base/errors.h"
#include "androidfw/Source.h"
@@ -54,6 +57,23 @@
return source_;
}
+bool RegularFile::GetModificationTime(struct tm* buf) const {
+ if (buf == nullptr) {
+ return false;
+ }
+ struct stat stat_buf;
+ if (stat(source_.path.c_str(), &stat_buf) != 0) {
+ return false;
+ }
+
+ struct tm* ptm;
+ struct tm tm_result;
+ ptm = localtime_r(&stat_buf.st_mtime, &tm_result);
+
+ *buf = *ptm;
+ return true;
+}
+
FileCollectionIterator::FileCollectionIterator(FileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
diff --git a/tools/aapt2/io/FileSystem.h b/tools/aapt2/io/FileSystem.h
index 0e798fc..f975196 100644
--- a/tools/aapt2/io/FileSystem.h
+++ b/tools/aapt2/io/FileSystem.h
@@ -32,6 +32,7 @@
std::unique_ptr<IData> OpenAsData() override;
std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
+ bool GetModificationTime(struct tm* buf) const override;
private:
DISALLOW_COPY_AND_ASSIGN(RegularFile);
diff --git a/tools/aapt2/io/ZipArchive.cpp b/tools/aapt2/io/ZipArchive.cpp
index 4a5385d..cb5bbe9 100644
--- a/tools/aapt2/io/ZipArchive.cpp
+++ b/tools/aapt2/io/ZipArchive.cpp
@@ -75,6 +75,14 @@
return zip_entry_.method != kCompressStored;
}
+bool ZipFile::GetModificationTime(struct tm* buf) const {
+ if (buf == nullptr) {
+ return false;
+ }
+ *buf = zip_entry_.GetModificationTime();
+ return true;
+}
+
ZipFileCollectionIterator::ZipFileCollectionIterator(
ZipFileCollection* collection)
: current_(collection->files_.begin()), end_(collection->files_.end()) {}
diff --git a/tools/aapt2/io/ZipArchive.h b/tools/aapt2/io/ZipArchive.h
index c263aa4..ac125d0 100644
--- a/tools/aapt2/io/ZipArchive.h
+++ b/tools/aapt2/io/ZipArchive.h
@@ -38,6 +38,7 @@
std::unique_ptr<io::InputStream> OpenInputStream() override;
const android::Source& GetSource() const override;
bool WasCompressed() override;
+ bool GetModificationTime(struct tm* buf) const override;
private:
::ZipArchiveHandle zip_handle_;
diff --git a/tools/aapt2/test/Common.h b/tools/aapt2/test/Common.h
index 83a0f3f..e48668c 100644
--- a/tools/aapt2/test/Common.h
+++ b/tools/aapt2/test/Common.h
@@ -98,6 +98,10 @@
return source_;
}
+ bool GetModificationTime(struct tm* buf) const override {
+ return false;
+ };
+
private:
DISALLOW_COPY_AND_ASSIGN(TestFile);