summaryrefslogtreecommitdiffstats
path: root/tzdata/tools/src
diff options
context:
space:
mode:
authorNeil Fuller <nfuller@google.com>2015-03-13 14:31:20 +0000
committerNeil Fuller <nfuller@google.com>2015-03-31 09:28:06 +0100
commit91c98d778c80e53a7f458264233375f982dcae14 (patch)
tree6d048b60f46c805e8a701e3af9798a4b1850a0c5 /tzdata/tools/src
parent1e342670cc46445bd51d53f7a28f95d1eb879c9f (diff)
downloadlibcore-91c98d778c80e53a7f458264233375f982dcae14.zip
libcore-91c98d778c80e53a7f458264233375f982dcae14.tar.gz
libcore-91c98d778c80e53a7f458264233375f982dcae14.tar.bz2
Timezone data installer code
The code here is used by the system server to install timezone data updates. It is separate so it can be tested. Scripts are included that build an "update bundle" (a zip file with a well-defined contents). ConfigBundle contains logic to extract the bundle safely. TzDataBundleBuilder is used in the test and tools to construct a bundle. The scripts in tools is a placeholder and will evolve. bionic/libc/tools/zoneinfo tools will likely move there so they can be integrated more closely. An app is included for testing updates. Bug: 19941636 Change-Id: Id0985f8c5be2f12858ee8bf52acf52bdb2df8741
Diffstat (limited to 'tzdata/tools/src')
-rw-r--r--tzdata/tools/src/main/libcore/tzdata/update/tools/CreateTzDataBundle.java127
-rw-r--r--tzdata/tools/src/main/libcore/tzdata/update/tools/TzDataBundleBuilder.java134
2 files changed, 261 insertions, 0 deletions
diff --git a/tzdata/tools/src/main/libcore/tzdata/update/tools/CreateTzDataBundle.java b/tzdata/tools/src/main/libcore/tzdata/update/tools/CreateTzDataBundle.java
new file mode 100644
index 0000000..cdb004a
--- /dev/null
+++ b/tzdata/tools/src/main/libcore/tzdata/update/tools/CreateTzDataBundle.java
@@ -0,0 +1,127 @@
+/*
+ * 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.
+ */
+package libcore.tzdata.update.tools;
+
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.Reader;
+import java.util.Properties;
+import libcore.tzdata.update.ConfigBundle;
+import libcore.tzdata.update.FileUtils;
+
+/**
+ * A command-line tool for creating a TZ data update bundle.
+ *
+ * Args:
+ * tzdata.properties file - the file describing the bundle (see template file in tzdata/tools)
+ * output file - the name of the file to be generated
+ */
+public class CreateTzDataBundle {
+
+ private CreateTzDataBundle() {}
+
+ public static void main(String[] args) throws Exception {
+ if (args.length != 2) {
+ printUsage();
+ System.exit(1);
+ }
+ File f = new File(args[0]);
+ if (!f.exists()) {
+ System.err.println("Properties file " + f + " not found");
+ printUsage();
+ System.exit(2);
+ }
+ Properties p = loadProperties(f);
+ TzDataBundleBuilder builder = new TzDataBundleBuilder()
+ .setTzDataVersion(getMandatoryProperty(p, "tzdata.version"))
+ .addBionicTzData(getMandatoryPropertyFile(p, "bionic.file"))
+ .addIcuTzData(getMandatoryPropertyFile(p, "icu.file"));
+
+ int i = 1;
+ while (true) {
+ String localFileNameProperty = "checksum.file.local." + i;
+ String localFileName = p.getProperty(localFileNameProperty);
+ String onDeviceFileNameProperty = "checksum.file.ondevice." + i;
+ String onDeviceFileName = p.getProperty(onDeviceFileNameProperty);
+ boolean foundLocalFileNameProperty = localFileName != null;
+ boolean foundOnDeviceFileNameProperty = onDeviceFileName != null;
+ if (!foundLocalFileNameProperty && !foundOnDeviceFileNameProperty) {
+ break;
+ } else if (foundLocalFileNameProperty != foundOnDeviceFileNameProperty) {
+ System.out.println("Properties file must specify both, or neither of: "
+ + localFileNameProperty + " and " + onDeviceFileNameProperty);
+ System.exit(5);
+ }
+
+ long checksum = FileUtils.calculateChecksum(new File(localFileName));
+ builder.addChecksum(onDeviceFileName, checksum);
+ i++;
+ }
+ if (i == 1) {
+ // For safety we enforce >= 1 checksum entry. The installer does not require it.
+ System.out.println("There must be at least one checksum file");
+ System.exit(6);
+ }
+ System.out.println("Update contains checksums for " + (i-1) + " files");
+
+ ConfigBundle bundle = builder.build();
+ File outputFile = new File(args[1]);
+ try (OutputStream os = new FileOutputStream(outputFile)) {
+ os.write(bundle.getBundleBytes());
+ }
+ System.out.println("Wrote: " + outputFile);
+ }
+
+ private static File getMandatoryPropertyFile(Properties p, String propertyName) {
+ String fileName = getMandatoryProperty(p, propertyName);
+ File file = new File(fileName);
+ if (!file.exists()) {
+ System.out.println(
+ "Missing file: " + file + " for property " + propertyName + " does not exist.");
+ printUsage();
+ System.exit(4);
+ }
+ return file;
+ }
+
+ private static String getMandatoryProperty(Properties p, String propertyName) {
+ String value = p.getProperty(propertyName);
+ if (value == null) {
+ System.out.println("Missing property: " + propertyName);
+ printUsage();
+ System.exit(3);
+ }
+ return value;
+ }
+
+ private static Properties loadProperties(File f) throws IOException {
+ Properties p = new Properties();
+ try (Reader reader = new InputStreamReader(new FileInputStream(f))) {
+ p.load(reader);
+ }
+ return p;
+ }
+
+ private static void printUsage() {
+ System.out.println("Usage:");
+ System.out.println("\t" + CreateTzDataBundle.class.getName() +
+ " <tzupdate.properties file> <output file>");
+ }
+}
diff --git a/tzdata/tools/src/main/libcore/tzdata/update/tools/TzDataBundleBuilder.java b/tzdata/tools/src/main/libcore/tzdata/update/tools/TzDataBundleBuilder.java
new file mode 100644
index 0000000..3550c6f
--- /dev/null
+++ b/tzdata/tools/src/main/libcore/tzdata/update/tools/TzDataBundleBuilder.java
@@ -0,0 +1,134 @@
+/*
+ * 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.
+ */
+package libcore.tzdata.update.tools;
+
+import java.io.ByteArrayOutputStream;
+import java.io.File;
+import java.io.FileInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipOutputStream;
+import libcore.tzdata.update.ConfigBundle;
+
+/**
+ * A class for creating a {@link ConfigBundle} containing timezone update data.
+ */
+public final class TzDataBundleBuilder {
+
+ private String tzDataVersion;
+ private StringBuilder checksumsFileContent = new StringBuilder();
+ private File zoneInfoFile;
+ private File icuTzDataFile;
+
+ public TzDataBundleBuilder setTzDataVersion(String tzDataVersion) {
+ this.tzDataVersion = tzDataVersion;
+ return this;
+ }
+
+ public TzDataBundleBuilder addChecksum(String fileName, long checksum) {
+ checksumsFileContent.append(Long.toString(checksum))
+ .append(',')
+ .append(fileName)
+ .append('\n');
+ return this;
+ }
+
+ public TzDataBundleBuilder addBionicTzData(File zoneInfoFile) {
+ this.zoneInfoFile = zoneInfoFile;
+ return this;
+ }
+
+ public TzDataBundleBuilder addIcuTzData(File icuTzDataFile) {
+ this.icuTzDataFile = icuTzDataFile;
+ return this;
+ }
+
+ /**
+ * Builds a {@link libcore.tzdata.update.ConfigBundle}.
+ */
+ public ConfigBundle build() throws IOException {
+ if (tzDataVersion == null) {
+ throw new IllegalStateException("Missing tzDataVersion");
+ }
+ if (zoneInfoFile == null) {
+ throw new IllegalStateException("Missing zoneInfo file");
+ }
+
+ return buildUnvalidated();
+ }
+
+ // For use in tests.
+ public TzDataBundleBuilder clearChecksumEntries() {
+ checksumsFileContent.setLength(0);
+ return this;
+ }
+
+ // For use in tests.
+ public TzDataBundleBuilder clearBionicTzData() {
+ this.zoneInfoFile = null;
+ return this;
+ }
+
+ /**
+ * For use in tests. Use {@link #build()}.
+ */
+ public ConfigBundle buildUnvalidated() throws IOException {
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (ZipOutputStream zos = new ZipOutputStream(baos)) {
+ addZipEntry(zos, ConfigBundle.CHECKSUMS_FILE_NAME,
+ checksumsFileContent.toString().getBytes(StandardCharsets.UTF_8));
+ if (tzDataVersion != null) {
+ addZipEntry(zos, ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
+ tzDataVersion.getBytes(StandardCharsets.UTF_8));
+ }
+ if (zoneInfoFile != null) {
+ addZipEntry(zos, ConfigBundle.ZONEINFO_FILE_NAME,
+ readFileAsByteArray(zoneInfoFile));
+ }
+ if (icuTzDataFile != null) {
+ addZipEntry(zos, ConfigBundle.ICU_DATA_FILE_NAME,
+ readFileAsByteArray(icuTzDataFile));
+ }
+ }
+ return new ConfigBundle(baos.toByteArray());
+ }
+
+ private static void addZipEntry(ZipOutputStream zos, String name, byte[] content)
+ throws IOException {
+ ZipEntry zipEntry = new ZipEntry(name);
+ zipEntry.setSize(content.length);
+ zos.putNextEntry(zipEntry);
+ zos.write(content);
+ zos.closeEntry();
+ }
+
+ /**
+ * Returns the contents of 'path' as a byte array.
+ */
+ public static byte[] readFileAsByteArray(File file) throws IOException {
+ byte[] buffer = new byte[8192];
+ ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ try (FileInputStream fis = new FileInputStream(file)) {
+ int count;
+ while ((count = fis.read(buffer)) != -1) {
+ baos.write(buffer, 0, count);
+ }
+ }
+ return baos.toByteArray();
+ }
+}
+