Home | History | Annotate | Download | only in tzdatacheck
      1 /*
      2  * Copyright (C) 2015 The Android Open Source Project
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  *      http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 #include <errno.h>
     18 #include <ftw.h>
     19 #include <libgen.h>
     20 #include <stdarg.h>
     21 #include <stdio.h>
     22 #include <stdlib.h>
     23 #include <string.h>
     24 #include <unistd.h>
     25 #include <iostream>
     26 #include <memory>
     27 #include <string>
     28 #include <vector>
     29 
     30 #include "android-base/logging.h"
     31 
     32 static const char* TZDATA_FILENAME = "/tzdata";
     33 // tzdata file header (as much as we need for the version):
     34 // byte[11] tzdata_version  -- e.g. "tzdata2012f"
     35 static const int TZ_HEADER_LENGTH = 11;
     36 
     37 static void usage() {
     38     std::cerr << "Usage: tzdatacheck SYSTEM_TZ_DIR DATA_TZ_DIR\n"
     39             "\n"
     40             "Compares the headers of two tzdata files. If the one in SYSTEM_TZ_DIR is the\n"
     41             "same or a higher version than the one in DATA_TZ_DIR the DATA_TZ_DIR is renamed\n"
     42             "and then deleted.\n";
     43     exit(1);
     44 }
     45 
     46 /*
     47  * Opens a file and fills headerBytes with the first byteCount bytes from the file. It is a fatal
     48  * error if the file is too small or cannot be opened. If the file does not exist false is returned.
     49  * If the bytes were read successfully then true is returned.
     50  */
     51 static bool readHeader(const std::string& tzDataFileName, char* headerBytes, size_t byteCount) {
     52     FILE* tzDataFile = fopen(tzDataFileName.c_str(), "r");
     53     if (tzDataFile == nullptr) {
     54         if (errno == ENOENT) {
     55             return false;
     56         } else {
     57             PLOG(FATAL) << "Error opening tzdata file " << tzDataFileName;
     58         }
     59     }
     60     size_t bytesRead = fread(headerBytes, 1, byteCount, tzDataFile);
     61     if (bytesRead != byteCount) {
     62         LOG(FATAL) << tzDataFileName << " is too small. " << byteCount << " bytes required";
     63     }
     64     fclose(tzDataFile);
     65     return true;
     66 }
     67 
     68 /* Checks the contents of headerBytes. It is a fatal error if it not a tzdata header. */
     69 static void checkValidHeader(const std::string& fileName, char* headerBytes) {
     70     if (strncmp("tzdata", headerBytes, 6) != 0) {
     71         LOG(FATAL) << fileName << " does not start with the expected bytes (tzdata)";
     72     }
     73 }
     74 
     75 /* Return the parent directory of dirName. */
     76 static std::string getParentDir(const std::string& dirName) {
     77     std::unique_ptr<char> mutable_dirname(strdup(dirName.c_str()));
     78     return dirname(mutable_dirname.get());
     79 }
     80 
     81 /* Deletes a single file, symlink or directory. Called from nftw(). */
     82 static int deleteFn(const char* fpath, const struct stat*, int typeflag, struct FTW*) {
     83     LOG(DEBUG) << "Inspecting " << fpath;
     84     switch (typeflag) {
     85     case FTW_F:
     86     case FTW_SL:
     87         LOG(DEBUG) << "Unlinking " << fpath;
     88         if (unlink(fpath)) {
     89             PLOG(WARNING) << "Failed to unlink file/symlink " << fpath;
     90         }
     91         break;
     92     case FTW_D:
     93     case FTW_DP:
     94         LOG(DEBUG) << "Removing dir " << fpath;
     95         if (rmdir(fpath)) {
     96             PLOG(WARNING) << "Failed to remove dir " << fpath;
     97         }
     98         break;
     99     default:
    100         LOG(WARNING) << "Unsupported file type " << fpath << ": " << typeflag;
    101         break;
    102     }
    103     return 0;
    104 }
    105 
    106 /*
    107  * Deletes dirToDelete and returns true if it is successful in removing or moving the directory out
    108  * of the way. If dirToDelete does not exist this function does nothing and returns true.
    109  *
    110  * During deletion, this function first renames the directory to a temporary name. If the temporary
    111  * directory cannot be created, or the directory cannot be renamed, false is returned. After the
    112  * rename, deletion of files and subdirs beneath the directory is performed on a "best effort"
    113  * basis. Symlinks beneath the directory are not followed.
    114  */
    115 static bool deleteDir(const std::string& dirToDelete) {
    116     // Check whether the dir exists.
    117     struct stat buf;
    118     if (stat(dirToDelete.c_str(), &buf) == 0) {
    119       if (!S_ISDIR(buf.st_mode)) {
    120         LOG(WARNING) << dirToDelete << " is not a directory";
    121         return false;
    122       }
    123     } else {
    124       if (errno == ENOENT) {
    125           PLOG(INFO) << "Directory does not exist: " << dirToDelete;
    126           return true;
    127       } else {
    128           PLOG(WARNING) << "Unable to stat " << dirToDelete;
    129           return false;
    130       }
    131     }
    132 
    133     // First, rename dirToDelete.
    134     std::string tempDirNameTemplate = getParentDir(dirToDelete);
    135     tempDirNameTemplate += "/tempXXXXXX";
    136 
    137     // Create an empty directory with the temporary name. For this we need a non-const char*.
    138     std::vector<char> tempDirName(tempDirNameTemplate.length() + 1);
    139     strcpy(&tempDirName[0], tempDirNameTemplate.c_str());
    140     if (mkdtemp(&tempDirName[0]) == nullptr) {
    141         PLOG(WARNING) << "Unable to create a temporary directory: " << tempDirNameTemplate;
    142         return false;
    143     }
    144 
    145     // Rename dirToDelete to tempDirName.
    146     int rc = rename(dirToDelete.c_str(), &tempDirName[0]);
    147     if (rc == -1) {
    148         PLOG(WARNING) << "Unable to rename directory from " << dirToDelete << " to "
    149                 << &tempDirName[0];
    150         return false;
    151     }
    152 
    153     // Recursively delete contents of tempDirName.
    154     rc = nftw(&tempDirName[0], deleteFn, 10 /* openFiles */,
    155             FTW_DEPTH | FTW_MOUNT | FTW_PHYS);
    156     if (rc == -1) {
    157         LOG(INFO) << "Could not delete directory: " << &tempDirName[0];
    158     }
    159     return true;
    160 }
    161 
    162 /*
    163  * After a platform update it is likely that timezone data found on the system partition will be
    164  * newer than the version found in the data partition. This tool detects this case and removes the
    165  * version in /data along with any update metadata.
    166  *
    167  * Note: This code is related to code in com.android.server.updates.TzDataInstallReceiver. The
    168  * paths for the metadata and current timezone data must match.
    169  *
    170  * Typically on device the two args will be:
    171  *   /system/usr/share/zoneinfo /data/misc/zoneinfo
    172  *
    173  * See usage() for usage notes.
    174  */
    175 int main(int argc, char* argv[]) {
    176     if (argc != 3) {
    177         usage();
    178     }
    179 
    180     const char* systemZoneInfoDir = argv[1];
    181     const char* dataZoneInfoDir = argv[2];
    182 
    183     std::string dataCurrentDirName(dataZoneInfoDir);
    184     dataCurrentDirName += "/current";
    185     std::string dataTzDataFileName(dataCurrentDirName);
    186     dataTzDataFileName += TZDATA_FILENAME;
    187 
    188     std::vector<char> dataTzDataHeader;
    189     dataTzDataHeader.reserve(TZ_HEADER_LENGTH);
    190 
    191     bool dataFileExists = readHeader(dataTzDataFileName, dataTzDataHeader.data(), TZ_HEADER_LENGTH);
    192     if (!dataFileExists) {
    193         LOG(INFO) << "tzdata file " << dataTzDataFileName << " does not exist. No action required.";
    194         return 0;
    195     }
    196     checkValidHeader(dataTzDataFileName, dataTzDataHeader.data());
    197 
    198     std::string systemTzDataFileName(systemZoneInfoDir);
    199     systemTzDataFileName += TZDATA_FILENAME;
    200     std::vector<char> systemTzDataHeader;
    201     systemTzDataHeader.reserve(TZ_HEADER_LENGTH);
    202     bool systemFileExists =
    203             readHeader(systemTzDataFileName, systemTzDataHeader.data(), TZ_HEADER_LENGTH);
    204     if (!systemFileExists) {
    205         LOG(FATAL) << systemTzDataFileName << " does not exist or could not be opened";
    206     }
    207     checkValidHeader(systemTzDataFileName, systemTzDataHeader.data());
    208 
    209     if (strncmp(&systemTzDataHeader[0], &dataTzDataHeader[0], TZ_HEADER_LENGTH) < 0) {
    210         LOG(INFO) << "tzdata file " << dataTzDataFileName << " is the newer than "
    211                 << systemTzDataFileName << ". No action required.";
    212     } else {
    213         // We have detected the case this tool is intended to prevent. Go fix it.
    214         LOG(INFO) << "tzdata file " << dataTzDataFileName << " is the same as or older than "
    215                 << systemTzDataFileName << "; fixing...";
    216 
    217         // Delete the update metadata
    218         std::string dataUpdatesDirName(dataZoneInfoDir);
    219         dataUpdatesDirName += "/updates";
    220         LOG(INFO) << "Removing: " << dataUpdatesDirName;
    221         bool deleted = deleteDir(dataUpdatesDirName);
    222         if (!deleted) {
    223             LOG(WARNING) << "Deletion of install metadata " << dataUpdatesDirName
    224                     << " was not successful";
    225         }
    226 
    227         // Delete the TZ data
    228         LOG(INFO) << "Removing: " << dataCurrentDirName;
    229         deleted = deleteDir(dataCurrentDirName);
    230         if (!deleted) {
    231             LOG(WARNING) << "Deletion of tzdata " << dataCurrentDirName << " was not successful";
    232         }
    233     }
    234 
    235     return 0;
    236 }
    237