191c98d778c80e53a7f458264233375f982dcae14Neil Fuller/*
291c98d778c80e53a7f458264233375f982dcae14Neil Fuller * Copyright (C) 2015 The Android Open Source Project
391c98d778c80e53a7f458264233375f982dcae14Neil Fuller *
491c98d778c80e53a7f458264233375f982dcae14Neil Fuller * Licensed under the Apache License, Version 2.0 (the "License");
591c98d778c80e53a7f458264233375f982dcae14Neil Fuller * you may not use this file except in compliance with the License.
691c98d778c80e53a7f458264233375f982dcae14Neil Fuller * You may obtain a copy of the License at
791c98d778c80e53a7f458264233375f982dcae14Neil Fuller *
891c98d778c80e53a7f458264233375f982dcae14Neil Fuller *      http://www.apache.org/licenses/LICENSE-2.0
991c98d778c80e53a7f458264233375f982dcae14Neil Fuller *
1091c98d778c80e53a7f458264233375f982dcae14Neil Fuller * Unless required by applicable law or agreed to in writing, software
1191c98d778c80e53a7f458264233375f982dcae14Neil Fuller * distributed under the License is distributed on an "AS IS" BASIS,
1291c98d778c80e53a7f458264233375f982dcae14Neil Fuller * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
1391c98d778c80e53a7f458264233375f982dcae14Neil Fuller * See the License for the specific language governing permissions and
1491c98d778c80e53a7f458264233375f982dcae14Neil Fuller * limitations under the License.
1591c98d778c80e53a7f458264233375f982dcae14Neil Fuller */
1691c98d778c80e53a7f458264233375f982dcae14Neil Fullerpackage libcore.tzdata.update;
1791c98d778c80e53a7f458264233375f982dcae14Neil Fuller
1891c98d778c80e53a7f458264233375f982dcae14Neil Fullerimport android.util.Slog;
1991c98d778c80e53a7f458264233375f982dcae14Neil Fuller
2091c98d778c80e53a7f458264233375f982dcae14Neil Fullerimport java.io.File;
2191c98d778c80e53a7f458264233375f982dcae14Neil Fullerimport java.io.IOException;
2291c98d778c80e53a7f458264233375f982dcae14Neil Fuller
2391c98d778c80e53a7f458264233375f982dcae14Neil Fuller/**
2491c98d778c80e53a7f458264233375f982dcae14Neil Fuller * A bundle-validation / extraction class. Separate from the services code that uses it for easier
2591c98d778c80e53a7f458264233375f982dcae14Neil Fuller * testing.
2691c98d778c80e53a7f458264233375f982dcae14Neil Fuller */
2791c98d778c80e53a7f458264233375f982dcae14Neil Fullerpublic final class TzDataBundleInstaller {
2891c98d778c80e53a7f458264233375f982dcae14Neil Fuller
2991c98d778c80e53a7f458264233375f982dcae14Neil Fuller    static final String CURRENT_TZ_DATA_DIR_NAME = "current";
3091c98d778c80e53a7f458264233375f982dcae14Neil Fuller    static final String WORKING_DIR_NAME = "working";
3191c98d778c80e53a7f458264233375f982dcae14Neil Fuller    static final String OLD_TZ_DATA_DIR_NAME = "old";
3291c98d778c80e53a7f458264233375f982dcae14Neil Fuller
3391c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private final String logTag;
3491c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private final File installDir;
3591c98d778c80e53a7f458264233375f982dcae14Neil Fuller
3691c98d778c80e53a7f458264233375f982dcae14Neil Fuller    public TzDataBundleInstaller(String logTag, File installDir) {
3791c98d778c80e53a7f458264233375f982dcae14Neil Fuller        this.logTag = logTag;
3891c98d778c80e53a7f458264233375f982dcae14Neil Fuller        this.installDir = installDir;
3991c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
4091c98d778c80e53a7f458264233375f982dcae14Neil Fuller
4191c98d778c80e53a7f458264233375f982dcae14Neil Fuller    /**
4291c98d778c80e53a7f458264233375f982dcae14Neil Fuller     * Install the supplied content.
4391c98d778c80e53a7f458264233375f982dcae14Neil Fuller     *
4491c98d778c80e53a7f458264233375f982dcae14Neil Fuller     * <p>Errors during unpacking or installation will throw an {@link IOException}.
4591c98d778c80e53a7f458264233375f982dcae14Neil Fuller     * If the content is invalid this method returns {@code false}.
4691c98d778c80e53a7f458264233375f982dcae14Neil Fuller     * If the installation completed successfully this method returns {@code true}.
4791c98d778c80e53a7f458264233375f982dcae14Neil Fuller     */
4891c98d778c80e53a7f458264233375f982dcae14Neil Fuller    public boolean install(byte[] content) throws IOException {
4991c98d778c80e53a7f458264233375f982dcae14Neil Fuller        File oldTzDataDir = new File(installDir, OLD_TZ_DATA_DIR_NAME);
5091c98d778c80e53a7f458264233375f982dcae14Neil Fuller        if (oldTzDataDir.exists()) {
5191c98d778c80e53a7f458264233375f982dcae14Neil Fuller            FileUtils.deleteRecursive(oldTzDataDir);
5291c98d778c80e53a7f458264233375f982dcae14Neil Fuller        }
5391c98d778c80e53a7f458264233375f982dcae14Neil Fuller
5491c98d778c80e53a7f458264233375f982dcae14Neil Fuller        File currentTzDataDir = new File(installDir, CURRENT_TZ_DATA_DIR_NAME);
5591c98d778c80e53a7f458264233375f982dcae14Neil Fuller        File workingDir = new File(installDir, WORKING_DIR_NAME);
5691c98d778c80e53a7f458264233375f982dcae14Neil Fuller
5791c98d778c80e53a7f458264233375f982dcae14Neil Fuller        Slog.i(logTag, "Applying time zone update");
5891c98d778c80e53a7f458264233375f982dcae14Neil Fuller        File unpackedContentDir = unpackBundle(content, workingDir);
5991c98d778c80e53a7f458264233375f982dcae14Neil Fuller        try {
6091c98d778c80e53a7f458264233375f982dcae14Neil Fuller            if (!checkBundleFilesExist(unpackedContentDir)) {
6191c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.i(logTag, "Update not applied: Bundle is missing files");
6291c98d778c80e53a7f458264233375f982dcae14Neil Fuller                return false;
6391c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
6491c98d778c80e53a7f458264233375f982dcae14Neil Fuller
6591c98d778c80e53a7f458264233375f982dcae14Neil Fuller            if (verifySystemChecksums(unpackedContentDir)) {
6691c98d778c80e53a7f458264233375f982dcae14Neil Fuller                FileUtils.makeDirectoryWorldAccessible(unpackedContentDir);
6791c98d778c80e53a7f458264233375f982dcae14Neil Fuller
6891c98d778c80e53a7f458264233375f982dcae14Neil Fuller                if (currentTzDataDir.exists()) {
6991c98d778c80e53a7f458264233375f982dcae14Neil Fuller                    Slog.i(logTag, "Moving " + currentTzDataDir + " to " + oldTzDataDir);
7091c98d778c80e53a7f458264233375f982dcae14Neil Fuller                    FileUtils.rename(currentTzDataDir, oldTzDataDir);
7191c98d778c80e53a7f458264233375f982dcae14Neil Fuller                }
7291c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.i(logTag, "Moving " + unpackedContentDir + " to " + currentTzDataDir);
7391c98d778c80e53a7f458264233375f982dcae14Neil Fuller                FileUtils.rename(unpackedContentDir, currentTzDataDir);
7491c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.i(logTag, "Update applied: " + currentTzDataDir + " successfully created");
7591c98d778c80e53a7f458264233375f982dcae14Neil Fuller                return true;
7691c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
7791c98d778c80e53a7f458264233375f982dcae14Neil Fuller            Slog.i(logTag, "Update not applied: System checksum did not match");
7891c98d778c80e53a7f458264233375f982dcae14Neil Fuller            return false;
7991c98d778c80e53a7f458264233375f982dcae14Neil Fuller        } finally {
8091c98d778c80e53a7f458264233375f982dcae14Neil Fuller            deleteBestEffort(oldTzDataDir);
8191c98d778c80e53a7f458264233375f982dcae14Neil Fuller            deleteBestEffort(unpackedContentDir);
8291c98d778c80e53a7f458264233375f982dcae14Neil Fuller        }
8391c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
8491c98d778c80e53a7f458264233375f982dcae14Neil Fuller
8591c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private void deleteBestEffort(File dir) {
8691c98d778c80e53a7f458264233375f982dcae14Neil Fuller        if (dir.exists()) {
8791c98d778c80e53a7f458264233375f982dcae14Neil Fuller            try {
8891c98d778c80e53a7f458264233375f982dcae14Neil Fuller                FileUtils.deleteRecursive(dir);
8991c98d778c80e53a7f458264233375f982dcae14Neil Fuller            } catch (IOException e) {
9091c98d778c80e53a7f458264233375f982dcae14Neil Fuller                // Logged but otherwise ignored.
9191c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.w(logTag, "Unable to delete " + dir, e);
9291c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
9391c98d778c80e53a7f458264233375f982dcae14Neil Fuller        }
9491c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
9591c98d778c80e53a7f458264233375f982dcae14Neil Fuller
9691c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private File unpackBundle(byte[] content, File targetDir) throws IOException {
9791c98d778c80e53a7f458264233375f982dcae14Neil Fuller        Slog.i(logTag, "Unpacking update content to: " + targetDir);
9891c98d778c80e53a7f458264233375f982dcae14Neil Fuller        ConfigBundle bundle = new ConfigBundle(content);
9991c98d778c80e53a7f458264233375f982dcae14Neil Fuller        bundle.extractTo(targetDir);
10091c98d778c80e53a7f458264233375f982dcae14Neil Fuller        return targetDir;
10191c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
10291c98d778c80e53a7f458264233375f982dcae14Neil Fuller
10391c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private boolean checkBundleFilesExist(File unpackedContentDir) throws IOException {
10491c98d778c80e53a7f458264233375f982dcae14Neil Fuller        Slog.i(logTag, "Verifying bundle contents");
10591c98d778c80e53a7f458264233375f982dcae14Neil Fuller        return FileUtils.filesExist(unpackedContentDir,
10691c98d778c80e53a7f458264233375f982dcae14Neil Fuller                ConfigBundle.TZ_DATA_VERSION_FILE_NAME,
10791c98d778c80e53a7f458264233375f982dcae14Neil Fuller                ConfigBundle.CHECKSUMS_FILE_NAME,
10891c98d778c80e53a7f458264233375f982dcae14Neil Fuller                ConfigBundle.ZONEINFO_FILE_NAME,
10991c98d778c80e53a7f458264233375f982dcae14Neil Fuller                ConfigBundle.ICU_DATA_FILE_NAME);
11091c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
11191c98d778c80e53a7f458264233375f982dcae14Neil Fuller
11291c98d778c80e53a7f458264233375f982dcae14Neil Fuller    private boolean verifySystemChecksums(File unpackedContentDir) throws IOException {
11391c98d778c80e53a7f458264233375f982dcae14Neil Fuller        Slog.i(logTag, "Verifying system file checksums");
11491c98d778c80e53a7f458264233375f982dcae14Neil Fuller        File checksumsFile = new File(unpackedContentDir, ConfigBundle.CHECKSUMS_FILE_NAME);
11591c98d778c80e53a7f458264233375f982dcae14Neil Fuller        for (String line : FileUtils.readLines(checksumsFile)) {
11691c98d778c80e53a7f458264233375f982dcae14Neil Fuller            int delimiterPos = line.indexOf(',');
11791c98d778c80e53a7f458264233375f982dcae14Neil Fuller            if (delimiterPos <= 0 || delimiterPos == line.length() - 1) {
11891c98d778c80e53a7f458264233375f982dcae14Neil Fuller                throw new IOException("Bad checksum entry: " + line);
11991c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
12091c98d778c80e53a7f458264233375f982dcae14Neil Fuller            long expectedChecksum;
12191c98d778c80e53a7f458264233375f982dcae14Neil Fuller            try {
12291c98d778c80e53a7f458264233375f982dcae14Neil Fuller                expectedChecksum = Long.parseLong(line.substring(0, delimiterPos));
12391c98d778c80e53a7f458264233375f982dcae14Neil Fuller            } catch (NumberFormatException e) {
12491c98d778c80e53a7f458264233375f982dcae14Neil Fuller                throw new IOException("Invalid checksum value: " + line);
12591c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
12691c98d778c80e53a7f458264233375f982dcae14Neil Fuller            String filePath = line.substring(delimiterPos + 1);
12791c98d778c80e53a7f458264233375f982dcae14Neil Fuller            File file = new File(filePath);
12891c98d778c80e53a7f458264233375f982dcae14Neil Fuller            if (!file.exists()) {
12991c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.i(logTag, "Failed checksum test for file: " + file + ": file not found");
13091c98d778c80e53a7f458264233375f982dcae14Neil Fuller                return false;
13191c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
13291c98d778c80e53a7f458264233375f982dcae14Neil Fuller            long actualChecksum = FileUtils.calculateChecksum(file);
13391c98d778c80e53a7f458264233375f982dcae14Neil Fuller            if (actualChecksum != expectedChecksum) {
13491c98d778c80e53a7f458264233375f982dcae14Neil Fuller                Slog.i(logTag, "Failed checksum test for file: " + file
13591c98d778c80e53a7f458264233375f982dcae14Neil Fuller                        + ": required=" + expectedChecksum + ", actual=" + actualChecksum);
13691c98d778c80e53a7f458264233375f982dcae14Neil Fuller                return false;
13791c98d778c80e53a7f458264233375f982dcae14Neil Fuller            }
13891c98d778c80e53a7f458264233375f982dcae14Neil Fuller        }
13991c98d778c80e53a7f458264233375f982dcae14Neil Fuller        return true;
14091c98d778c80e53a7f458264233375f982dcae14Neil Fuller    }
14191c98d778c80e53a7f458264233375f982dcae14Neil Fuller}
142