blob: a8df2b15d4d26985745b2173e46f5c61ceb36792 [file] [log] [blame]
// Copyright 2018 The Chromium OS Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "arc/apk-cache/cache_cleaner.h"
#include <fnmatch.h>
#include <memory>
#include <string>
#include <unordered_set>
#include <vector>
#include <base/files/file.h>
#include <base/files/file_enumerator.h>
#include <base/files/file_path.h>
#include <base/files/file_util.h>
#include <base/json/json_reader.h>
#include <base/logging.h>
#include <base/time/time.h>
#include <base/values.h>
#include "arc/apk-cache/apk_cache_utils.h"
#include "arc/apk-cache/cache_cleaner_db.h"
#include "arc/apk-cache/cache_cleaner_utils.h"
namespace apk_cache {
constexpr char kAttrJson[] = "attr.json";
constexpr char kApkExtension[] = ".apk";
constexpr char kObbExtension[] = ".obb";
constexpr char kMainObbPrefix[] = "main.";
constexpr char kPatchObbPrefix[] = "patch.";
namespace {
constexpr char kKeyAttributesAtime[] = "attributes.atime";
// Removes all the files (if any) from cache root. Does not remove
// directories. Returns true if all the intended files were deleted.
bool RemoveUnexpectedFilesFromCacheRoot(const base::FilePath& cache_root) {
std::unordered_set<std::string> database_files(
kDatabaseFiles, kDatabaseFiles + kDatabaseFilesCount);
return RemoveUnexpectedItemsFromDir(
cache_root,
base::FileEnumerator::FileType::FILES |
base::FileEnumerator::FileType::SHOW_SYM_LINKS,
database_files);
}
// Returns if |file_name| matches the |pattern|.
bool IsMatch(const std::string& file_name, const std::string& pattern) {
return fnmatch(pattern.c_str(), file_name.c_str(), FNM_NOESCAPE) == 0;
}
// Parses contents of the attributes JSON file and verifies that last access
// time of the package was at most 30 days ago.
bool IsAccessTimeValid(const base::StringPiece& json_message) {
auto root = base::JSONReader::ReadAndReturnValueWithError(
json_message, base::JSON_PARSE_RFC);
if (!root.value) {
LOG(ERROR) << "Reading attributes JSON failed (error message: "
<< root.error_message << ").";
return false;
}
if (!root.value->is_dict()) {
LOG(ERROR) << "Could not interpret the JSON as a dictionary.";
return false;
}
const std::string* atime_str =
root.value->FindStringPath(kKeyAttributesAtime);
if (!atime_str) {
LOG(ERROR) << "Could not read the value of the access time with the "
<< kKeyAttributesAtime << " key.";
return false;
}
base::Time atime;
if (!base::Time::FromString(atime_str->c_str(), &atime)) {
LOG(ERROR) << "Can not parse the date: " << *atime_str;
return false;
}
const base::TimeDelta age = base::Time::Now() - atime;
return age < kValidityPeriod;
}
// Verifies that package directory contains all the necessary files, does
// not contain any extra files/directories and was accessed within last 30 days.
// Returns true if the package is valid and should be kept in the cache.
bool IsPackageValid(const base::FilePath& package_path) {
// Package directory must contain:
// 1. One .apk file
// 2. One attr.json file
// 3. No or one main .obb file.
// 4. No or one patch .obb file.
// 5. No other files or directories.
base::FileEnumerator files(package_path, false /* recursive */,
base::FileEnumerator::DIRECTORIES |
base::FileEnumerator::FILES |
base::FileEnumerator::SHOW_SYM_LINKS);
int apk_count = 0;
int attr_count = 0;
int main_obb_count = 0;
int patch_obb_count = 0;
const std::string package_name = package_path.BaseName().value();
const std::string apk_file_name = package_name + kApkExtension;
const std::string main_obb_file_name =
std::string(kMainObbPrefix) + "*" + package_name + kObbExtension;
const std::string patch_obb_file_name =
std::string(kPatchObbPrefix) + "*" + package_name + kObbExtension;
for (base::FilePath file_path = files.Next(); !file_path.empty();
file_path = files.Next()) {
if (base::DirectoryExists(file_path)) {
LOG(INFO) << "There are directories in " << package_path.value();
return false;
}
const std::string file_name = file_path.BaseName().value();
if (IsMatch(file_name, apk_file_name)) {
apk_count++;
} else if (IsMatch(file_name, kAttrJson)) {
attr_count++;
} else if (IsMatch(file_name, main_obb_file_name)) {
main_obb_count++;
} else if (IsMatch(file_name, patch_obb_file_name)) {
patch_obb_count++;
} else {
LOG(INFO) << package_name << " contains unnecessary files.";
return false;
}
}
if (apk_count != 1) {
LOG(INFO) << "Number of APK files is not equal to 1 in " << package_name;
return false;
}
if (attr_count != 1) {
LOG(INFO) << "Number of JSON attributes files is " << attr_count
<< " which not equal to 1 in " << package_name;
return false;
}
if (main_obb_count > 1) {
LOG(INFO) << "Number of patch OBB files is " << main_obb_count
<< ", which greater then 1 in " << package_name;
return false;
}
if (patch_obb_count > 1) {
LOG(INFO) << "Number of patch OBB files is " << patch_obb_count
<< ", which greater then 1 in " << package_name;
return false;
}
const base::FilePath attr_file_path = package_path.Append(kAttrJson);
std::string attr_json_contents;
if (!base::ReadFileToString(attr_file_path, &attr_json_contents)) {
LOG(ERROR) << "Could not read the attributes file.";
return false;
}
return IsAccessTimeValid(attr_json_contents);
}
} // namespace
bool Clean(const base::FilePath& cache_path) {
if (!base::DirectoryExists(cache_path)) {
LOG(ERROR) << "APK cache directory " << cache_path.value()
<< " does not exist";
return false;
}
bool success = RemoveUnexpectedFilesFromCacheRoot(cache_path);
base::FileEnumerator packages(
cache_path, false /* recursive */,
base::FileEnumerator::DIRECTORIES | base::FileEnumerator::SHOW_SYM_LINKS);
for (base::FilePath package_path = packages.Next(); !package_path.empty();
package_path = packages.Next()) {
// Skip |files| directory which should be managed by database cleaner.
if (package_path.BaseName().MaybeAsASCII() == std::string(kFilesBase))
continue;
if (!IsPackageValid(package_path)) {
if (!base::DeletePathRecursively(package_path)) {
LOG(ERROR) << "Error deletion path " << package_path.value();
success = false;
}
} else {
LOG(INFO) << "Package " << package_path.value() << " looks OK.";
}
}
return success;
}
} // namespace apk_cache