diff options
| -rw-r--r-- | cmds/installd/Android.bp | 19 | ||||
| -rw-r--r-- | cmds/installd/dexopt.cpp | 179 | ||||
| -rw-r--r-- | cmds/installd/restorable_file.cpp | 161 | ||||
| -rw-r--r-- | cmds/installd/restorable_file.h | 107 | ||||
| -rw-r--r-- | cmds/installd/tests/Android.bp | 20 | ||||
| -rw-r--r-- | cmds/installd/tests/installd_file_test.cpp | 521 | ||||
| -rw-r--r-- | cmds/installd/tests/installd_file_test.xml | 35 |
7 files changed, 940 insertions, 102 deletions
diff --git a/cmds/installd/Android.bp b/cmds/installd/Android.bp index faa84854ba..00babc3346 100644 --- a/cmds/installd/Android.bp +++ b/cmds/installd/Android.bp @@ -28,6 +28,7 @@ cc_defaults { "dexopt.cpp", "execv_helper.cpp", "globals.cpp", + "restorable_file.cpp", "run_dex2oat.cpp", "unique_file.cpp", "utils.cpp", @@ -80,7 +81,7 @@ cc_defaults { "-cert-err58-cpp", ], tidy_flags: [ - "-warnings-as-errors=clang-analyzer-security*,cert-*" + "-warnings-as-errors=clang-analyzer-security*,cert-*", ], } @@ -132,7 +133,10 @@ cc_test_host { "unique_file.cpp", "execv_helper.cpp", ], - cflags: ["-Wall", "-Werror"], + cflags: [ + "-Wall", + "-Werror", + ], shared_libs: [ "libbase", "server_configurable_flags", @@ -170,7 +174,7 @@ cc_binary { // Needs to be wherever installd is as it's execed by // installd. - required: [ "migrate_legacy_obb_data.sh" ], + required: ["migrate_legacy_obb_data.sh"], } // OTA chroot tool @@ -194,7 +198,7 @@ cc_binary { "libutils", ], required: [ - "apexd" + "apexd", ], } @@ -213,7 +217,7 @@ cc_library_static { name: "libotapreoptparameters", cflags: [ "-Wall", - "-Werror" + "-Werror", ], srcs: ["otapreopt_parameters.cpp"], @@ -237,7 +241,7 @@ cc_binary { name: "otapreopt", cflags: [ "-Wall", - "-Werror" + "-Werror", ], srcs: [ @@ -246,6 +250,7 @@ cc_binary { "globals.cpp", "otapreopt.cpp", "otapreopt_utils.cpp", + "restorable_file.cpp", "run_dex2oat.cpp", "unique_file.cpp", "utils.cpp", @@ -296,5 +301,5 @@ sh_binary { // Script to migrate legacy obb data. sh_binary { name: "migrate_legacy_obb_data.sh", - src: "migrate_legacy_obb_data.sh" + src: "migrate_legacy_obb_data.sh", } diff --git a/cmds/installd/dexopt.cpp b/cmds/installd/dexopt.cpp index 2bcf2d473d..b6f42ad172 100644 --- a/cmds/installd/dexopt.cpp +++ b/cmds/installd/dexopt.cpp @@ -58,9 +58,10 @@ #include "dexopt_return_codes.h" #include "execv_helper.h" #include "globals.h" -#include "installd_deps.h" #include "installd_constants.h" +#include "installd_deps.h" #include "otapreopt_utils.h" +#include "restorable_file.h" #include "run_dex2oat.h" #include "unique_file.h" #include "utils.h" @@ -309,12 +310,6 @@ static bool IsBootClassPathProfilingEnable() { return profile_boot_class_path == "true"; } -static void UnlinkIgnoreResult(const std::string& path) { - if (unlink(path.c_str()) < 0) { - PLOG(ERROR) << "Failed to unlink " << path; - } -} - /* * Whether dexopt should use a swap file when compiling an APK. * @@ -988,42 +983,34 @@ static bool create_oat_out_path(const char* apk_path, const char* instruction_se } // (re)Creates the app image if needed. -UniqueFile maybe_open_app_image(const std::string& out_oat_path, - bool generate_app_image, bool is_public, int uid, bool is_secondary_dex) { - +RestorableFile maybe_open_app_image(const std::string& out_oat_path, bool generate_app_image, + bool is_public, int uid, bool is_secondary_dex) { const std::string image_path = create_image_filename(out_oat_path); if (image_path.empty()) { // Happens when the out_oat_path has an unknown extension. - return UniqueFile(); + return RestorableFile(); } - // In case there is a stale image, remove it now. Ignore any error. - unlink(image_path.c_str()); - // Not enabled, exit. if (!generate_app_image) { - return UniqueFile(); + RestorableFile::RemoveAllFiles(image_path); + return RestorableFile(); } std::string app_image_format = GetProperty("dalvik.vm.appimageformat", ""); if (app_image_format.empty()) { - return UniqueFile(); + RestorableFile::RemoveAllFiles(image_path); + return RestorableFile(); } - // Recreate is true since we do not want to modify a mapped image. If the app is - // already running and we modify the image file, it can cause crashes (b/27493510). - UniqueFile image_file( - open_output_file(image_path.c_str(), true /*recreate*/, 0600 /*permissions*/), - image_path, - UnlinkIgnoreResult); + // If the app is already running and we modify the image file, it can cause crashes + // (b/27493510). + RestorableFile image_file = RestorableFile::CreateWritableFile(image_path, + /*permissions*/ 0600); if (image_file.fd() < 0) { // Could not create application image file. Go on since we can compile without it. LOG(ERROR) << "installd could not create '" << image_path << "' for image file during dexopt"; - // If we have a valid image file path but no image fd, explicitly erase the image file. - if (unlink(image_path.c_str()) < 0) { - if (errno != ENOENT) { - PLOG(ERROR) << "Couldn't unlink image file " << image_path; - } - } + // If we have a valid image file path but cannot create tmp file, reset it. + image_file.reset(); } else if (!set_permissions_and_ownership( image_file.fd(), is_public, uid, image_path.c_str(), is_secondary_dex)) { ALOGE("installd cannot set owner '%s' for image during dexopt\n", image_path.c_str()); @@ -1097,9 +1084,9 @@ UniqueFile maybe_open_reference_profile(const std::string& pkgname, // Opens the vdex files and assigns the input fd to in_vdex_wrapper and the output fd to // out_vdex_wrapper. Returns true for success or false in case of errors. bool open_vdex_files_for_dex2oat(const char* apk_path, const char* out_oat_path, int dexopt_needed, - const char* instruction_set, bool is_public, int uid, bool is_secondary_dex, - bool profile_guided, UniqueFile* in_vdex_wrapper, - UniqueFile* out_vdex_wrapper) { + const char* instruction_set, bool is_public, int uid, + bool is_secondary_dex, bool profile_guided, + UniqueFile* in_vdex_wrapper, RestorableFile* out_vdex_wrapper) { CHECK(in_vdex_wrapper != nullptr); CHECK(out_vdex_wrapper != nullptr); // Open the existing VDEX. We do this before creating the new output VDEX, which will @@ -1114,6 +1101,14 @@ bool open_vdex_files_for_dex2oat(const char* apk_path, const char* out_oat_path, return false; } + // Create work file first. All files will be deleted when it fails. + *out_vdex_wrapper = RestorableFile::CreateWritableFile(out_vdex_path_str, + /*permissions*/ 0644); + if (out_vdex_wrapper->fd() < 0) { + ALOGE("installd cannot open vdex '%s' during dexopt\n", out_vdex_path_str.c_str()); + return false; + } + bool update_vdex_in_place = false; if (dexopt_action != DEX2OAT_FROM_SCRATCH) { // Open the possibly existing vdex. If none exist, we pass -1 to dex2oat for input-vdex-fd. @@ -1145,41 +1140,19 @@ bool open_vdex_files_for_dex2oat(const char* apk_path, const char* out_oat_path, (dexopt_action == DEX2OAT_FOR_BOOT_IMAGE) && !profile_guided; if (update_vdex_in_place) { + // dex2oat marks it invalid anyway. So delete it and set work file fd. + unlink(in_vdex_path_str.c_str()); // Open the file read-write to be able to update it. - in_vdex_wrapper->reset(open(in_vdex_path_str.c_str(), O_RDWR, 0), - in_vdex_path_str); - if (in_vdex_wrapper->fd() == -1) { - // If we failed to open the file, we cannot update it in place. - update_vdex_in_place = false; - } + in_vdex_wrapper->reset(out_vdex_wrapper->fd(), in_vdex_path_str); + // Disable auto close for the in wrapper fd (it will be done when destructing the out + // wrapper). + in_vdex_wrapper->DisableAutoClose(); } else { in_vdex_wrapper->reset(open(in_vdex_path_str.c_str(), O_RDONLY, 0), in_vdex_path_str); } } - // If we are updating the vdex in place, we do not need to recreate a vdex, - // and can use the same existing one. - if (update_vdex_in_place) { - // We unlink the file in case the invocation of dex2oat fails, to ensure we don't - // have bogus stale vdex files. - out_vdex_wrapper->reset( - in_vdex_wrapper->fd(), - out_vdex_path_str, - UnlinkIgnoreResult); - // Disable auto close for the in wrapper fd (it will be done when destructing the out - // wrapper). - in_vdex_wrapper->DisableAutoClose(); - } else { - out_vdex_wrapper->reset( - open_output_file(out_vdex_path_str.c_str(), /*recreate*/true, /*permissions*/0644), - out_vdex_path_str, - UnlinkIgnoreResult); - if (out_vdex_wrapper->fd() < 0) { - ALOGE("installd cannot open vdex'%s' during dexopt\n", out_vdex_path_str.c_str()); - return false; - } - } if (!set_permissions_and_ownership(out_vdex_wrapper->fd(), is_public, uid, out_vdex_path_str.c_str(), is_secondary_dex)) { ALOGE("installd cannot set owner '%s' for vdex during dexopt\n", out_vdex_path_str.c_str()); @@ -1191,16 +1164,13 @@ bool open_vdex_files_for_dex2oat(const char* apk_path, const char* out_oat_path, } // Opens the output oat file for the given apk. -UniqueFile open_oat_out_file(const char* apk_path, const char* oat_dir, - bool is_public, int uid, const char* instruction_set, bool is_secondary_dex) { +RestorableFile open_oat_out_file(const char* apk_path, const char* oat_dir, bool is_public, int uid, + const char* instruction_set, bool is_secondary_dex) { char out_oat_path[PKG_PATH_MAX]; if (!create_oat_out_path(apk_path, instruction_set, oat_dir, is_secondary_dex, out_oat_path)) { - return UniqueFile(); + return RestorableFile(); } - UniqueFile oat( - open_output_file(out_oat_path, /*recreate*/true, /*permissions*/0644), - out_oat_path, - UnlinkIgnoreResult); + RestorableFile oat = RestorableFile::CreateWritableFile(out_oat_path, /*permissions*/ 0644); if (oat.fd() < 0) { PLOG(ERROR) << "installd cannot open output during dexopt" << out_oat_path; } else if (!set_permissions_and_ownership( @@ -1839,6 +1809,7 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins if (sec_dex_result == kSecondaryDexOptProcessOk) { oat_dir = oat_dir_str.c_str(); if (dexopt_needed == NO_DEXOPT_NEEDED) { + *completed = true; return 0; // Nothing to do, report success. } } else if (sec_dex_result == kSecondaryDexOptProcessCancelled) { @@ -1874,8 +1845,8 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins } // Create the output OAT file. - UniqueFile out_oat = open_oat_out_file(dex_path, oat_dir, is_public, uid, - instruction_set, is_secondary_dex); + RestorableFile out_oat = + open_oat_out_file(dex_path, oat_dir, is_public, uid, instruction_set, is_secondary_dex); if (out_oat.fd() < 0) { *error_msg = "Could not open out oat file."; return -1; @@ -1883,7 +1854,7 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins // Open vdex files. UniqueFile in_vdex; - UniqueFile out_vdex; + RestorableFile out_vdex; if (!open_vdex_files_for_dex2oat(dex_path, out_oat.path().c_str(), dexopt_needed, instruction_set, is_public, uid, is_secondary_dex, profile_guided, &in_vdex, &out_vdex)) { @@ -1919,8 +1890,8 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins } // Create the app image file if needed. - UniqueFile out_image = maybe_open_app_image( - out_oat.path(), generate_app_image, is_public, uid, is_secondary_dex); + RestorableFile out_image = maybe_open_app_image(out_oat.path(), generate_app_image, is_public, + uid, is_secondary_dex); UniqueFile dex_metadata; if (dex_metadata_path != nullptr) { @@ -1953,30 +1924,18 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins LOG(VERBOSE) << "DexInv: --- BEGIN '" << dex_path << "' ---"; RunDex2Oat runner(dex2oat_bin, execv_helper.get()); - runner.Initialize(out_oat, - out_vdex, - out_image, - in_dex, - in_vdex, - dex_metadata, - reference_profile, - class_loader_context, - join_fds(context_input_fds), - swap_fd.get(), - instruction_set, - compiler_filter, - debuggable, - boot_complete, - for_restore, - target_sdk_version, - enable_hidden_api_checks, - generate_compact_dex, - use_jitzygote_image, + runner.Initialize(out_oat.GetUniqueFile(), out_vdex.GetUniqueFile(), out_image.GetUniqueFile(), + in_dex, in_vdex, dex_metadata, reference_profile, class_loader_context, + join_fds(context_input_fds), swap_fd.get(), instruction_set, compiler_filter, + debuggable, boot_complete, for_restore, target_sdk_version, + enable_hidden_api_checks, generate_compact_dex, use_jitzygote_image, compilation_reason); bool cancelled = false; pid_t pid = dexopt_status_->check_cancellation_and_fork(&cancelled); if (cancelled) { + *completed = false; + reference_profile.DisableCleanup(); return 0; } if (pid == 0) { @@ -2004,6 +1963,7 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- cancelled"; // cancelled, not an error *completed = false; + reference_profile.DisableCleanup(); return 0; } LOG(VERBOSE) << "DexInv: --- END '" << dex_path << "' --- status=0x" @@ -2013,13 +1973,42 @@ int dexopt(const char* dex_path, uid_t uid, const char* pkgname, const char* ins } } - // TODO(b/156537504) Implement SWAP of completed files - // We've been successful, don't delete output. - out_oat.DisableCleanup(); - out_vdex.DisableCleanup(); - out_image.DisableCleanup(); + // dex2oat ran successfully, so profile is safe to keep. reference_profile.DisableCleanup(); + // We've been successful, commit work files. + // If committing (=renaming tmp to regular) fails, try to restore backup files. + // If restoring fails as well, as a last resort, remove all files. + if (!out_oat.CreateBackupFile() || !out_vdex.CreateBackupFile() || + !out_image.CreateBackupFile()) { + // Renaming failure can mean that the original file may not be accessible from installd. + LOG(ERROR) << "Cannot create backup file from existing file, file in wrong state?" + << ", out_oat:" << out_oat.path() << " ,out_vdex:" << out_vdex.path() + << " ,out_image:" << out_image.path(); + out_oat.ResetAndRemoveAllFiles(); + out_vdex.ResetAndRemoveAllFiles(); + out_image.ResetAndRemoveAllFiles(); + return -1; + } + if (!out_oat.CommitWorkFile() || !out_vdex.CommitWorkFile() || !out_image.CommitWorkFile()) { + LOG(ERROR) << "Cannot commit, out_oat:" << out_oat.path() + << " ,out_vdex:" << out_vdex.path() << " ,out_image:" << out_image.path(); + if (!out_oat.RestoreBackupFile() || !out_vdex.RestoreBackupFile() || + !out_image.RestoreBackupFile()) { + LOG(ERROR) << "Cannot cancel commit, out_oat:" << out_oat.path() + << " ,out_vdex:" << out_vdex.path() << " ,out_image:" << out_image.path(); + // Restoring failed. + out_oat.ResetAndRemoveAllFiles(); + out_vdex.ResetAndRemoveAllFiles(); + out_image.ResetAndRemoveAllFiles(); + } + return -1; + } + // Now remove remaining backup files. + out_oat.RemoveBackupFile(); + out_vdex.RemoveBackupFile(); + out_image.RemoveBackupFile(); + *completed = true; return 0; } diff --git a/cmds/installd/restorable_file.cpp b/cmds/installd/restorable_file.cpp new file mode 100644 index 0000000000..fd54a232d5 --- /dev/null +++ b/cmds/installd/restorable_file.cpp @@ -0,0 +1,161 @@ +/* + * Copyright (C) 2021 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. + */ + +#include "restorable_file.h" + +#include <string> + +#include <fcntl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <unistd.h> + +#include <android-base/logging.h> +#include <android-base/stringprintf.h> + +namespace { + +constexpr char kTmpFileSuffix[] = ".tmp"; +constexpr char kBackupFileSuffix[] = ".backup"; + +std::string GetTmpFilePath(const std::string& path) { + return android::base::StringPrintf("%s%s", path.c_str(), kTmpFileSuffix); +} + +std::string GetBackupFilePath(const std::string& path) { + return android::base::StringPrintf("%s%s", path.c_str(), kBackupFileSuffix); +} + +void UnlinkPossiblyNonExistingFile(const std::string& path) { + if (unlink(path.c_str()) < 0) { + if (errno != ENOENT && errno != EROFS) { // EROFS reported even if it does not exist. + PLOG(ERROR) << "Cannot unlink: " << path; + } + } +} + +// Check if file for the given path exists +bool FileExists(const std::string& path) { + struct stat st; + return ::stat(path.c_str(), &st) == 0; +} + +} // namespace + +namespace android { +namespace installd { + +RestorableFile::RestorableFile() : RestorableFile(-1, "") {} + +RestorableFile::RestorableFile(int value, const std::string& path) : unique_file_(value, path) { + // As cleanup is null, this does not make much difference but use unique_file_ only for closing + // tmp file. + unique_file_.DisableCleanup(); +} + +RestorableFile::~RestorableFile() { + reset(); +} + +void RestorableFile::reset() { + // need to copy before reset clears it. + std::string path(unique_file_.path()); + unique_file_.reset(); + if (!path.empty()) { + UnlinkPossiblyNonExistingFile(GetTmpFilePath(path)); + } +} + +bool RestorableFile::CreateBackupFile() { + if (path().empty() || !FileExists(path())) { + return true; + } + std::string backup = GetBackupFilePath(path()); + UnlinkPossiblyNonExistingFile(backup); + if (rename(path().c_str(), backup.c_str()) < 0) { + PLOG(ERROR) << "Cannot rename " << path() << " to " << backup; + return false; + } + return true; +} + +bool RestorableFile::CommitWorkFile() { + std::string path(unique_file_.path()); + // Keep the path with Commit for debugging purpose. + unique_file_.reset(-1, path); + if (!path.empty()) { + if (rename(GetTmpFilePath(path).c_str(), path.c_str()) < 0) { + PLOG(ERROR) << "Cannot rename " << GetTmpFilePath(path) << " to " << path; + // Remove both files as renaming can fail due to the original file as well. + UnlinkPossiblyNonExistingFile(path); + UnlinkPossiblyNonExistingFile(GetTmpFilePath(path)); + return false; + } + } + + return true; +} + +bool RestorableFile::RestoreBackupFile() { + std::string backup = GetBackupFilePath(path()); + if (path().empty() || !FileExists(backup)) { + return true; + } + UnlinkPossiblyNonExistingFile(path()); + if (rename(backup.c_str(), path().c_str()) < 0) { + PLOG(ERROR) << "Cannot rename " << backup << " to " << path(); + return false; + } + return true; +} + +void RestorableFile::RemoveBackupFile() { + UnlinkPossiblyNonExistingFile(GetBackupFilePath(path())); +} + +const UniqueFile& RestorableFile::GetUniqueFile() const { + return unique_file_; +} + +void RestorableFile::ResetAndRemoveAllFiles() { + std::string path(unique_file_.path()); + reset(); + RemoveAllFiles(path); +} + +RestorableFile RestorableFile::CreateWritableFile(const std::string& path, int permissions) { + std::string tmp_file_path = GetTmpFilePath(path); + // If old tmp file exists, delete it. + UnlinkPossiblyNonExistingFile(tmp_file_path); + int fd = -1; + if (!path.empty()) { + fd = open(tmp_file_path.c_str(), O_RDWR | O_CREAT, permissions); + if (fd < 0) { + PLOG(ERROR) << "Cannot create file: " << tmp_file_path; + } + } + RestorableFile rf(fd, path); + return rf; +} + +void RestorableFile::RemoveAllFiles(const std::string& path) { + UnlinkPossiblyNonExistingFile(GetTmpFilePath(path)); + UnlinkPossiblyNonExistingFile(GetBackupFilePath(path)); + UnlinkPossiblyNonExistingFile(path); +} + +} // namespace installd +} // namespace android diff --git a/cmds/installd/restorable_file.h b/cmds/installd/restorable_file.h new file mode 100644 index 0000000000..eda229241a --- /dev/null +++ b/cmds/installd/restorable_file.h @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2021 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. + */ + +#ifndef ANDROID_INSTALLD_RESTORABLE_FILE_H +#define ANDROID_INSTALLD_RESTORABLE_FILE_H + +#include <functional> +#include <string> + +#include "unique_file.h" + +namespace android { +namespace installd { + +// This is a file abstraction which allows restoring to the original file while temporary work +// file is updated. +// +// Typical flow for this API will be: +// RestorableFile rf = RestorableFile::CreateWritableFile(...) +// write to file using file descriptor acquired from: rf.fd() +// Make work file into a regular file with: rf.CommitWorkFile() +// Or throw away the work file by destroying the instance without calling CommitWorkFile(). +// The temporary work file is closed / removed when an instance is destroyed without calling +// CommitWorkFile(). The original file, if CommitWorkFile() is not called, will be kept. +// +// For safer restoration of original file when commit fails, following 3 steps can be taken: +// 1. CreateBackupFile(): This renames an existing regular file into a separate backup file. +// 2. CommitWorkFile(): Rename the work file into the regular file. +// 3. RemoveBackupFile(): Removes the backup file +// If CommitWorkFile fails, client can call RestoreBackupFile() which will restore regular file from +// the backup. +class RestorableFile { +public: + // Creates invalid instance with no fd (=-1) and empty path. + RestorableFile(); + RestorableFile(RestorableFile&& other) = default; + ~RestorableFile(); + + // Passes all contents of other file into the current file. + // Files kept for the current file will be either deleted or committed depending on + // CommitWorkFile() and DisableCleanUp() calls made before this. + RestorableFile& operator=(RestorableFile&& other) = default; + + // Gets file descriptor for backing work (=temporary) file. If work file does not exist, it will + // return -1. + int fd() const { return unique_file_.fd(); } + + // Gets the path name for the regular file (not temporary file). + const std::string& path() const { return unique_file_.path(); } + + // Closes work file, deletes it and resets all internal states into default states. + void reset(); + + // Closes work file and closes all files including work file, backup file and regular file. + void ResetAndRemoveAllFiles(); + + // Creates a backup file by renaming existing regular file. This will return false if renaming + // fails. If regular file for renaming does not exist, it will return true. + bool CreateBackupFile(); + + // Closes existing work file and makes it a regular file. + // Note that the work file is closed and fd() will return -1 after this. path() will still + // return the original path. + // This will return false when committing fails (=cannot rename). Both the regular file and tmp + // file will be deleted when it fails. + bool CommitWorkFile(); + + // Cancels the commit and restores the backup file into the regular one. If renaming fails, + // it will return false. This returns true if the backup file does not exist. + bool RestoreBackupFile(); + + // Removes the backup file. + void RemoveBackupFile(); + + // Gets UniqueFile with the same path and fd() pointing to the work file. + const UniqueFile& GetUniqueFile() const; + + // Creates writable RestorableFile. This involves creating tmp file for writing. + static RestorableFile CreateWritableFile(const std::string& path, int permissions); + + // Removes the specified file together with tmp file generated as RestorableFile. + static void RemoveAllFiles(const std::string& path); + +private: + RestorableFile(int value, const std::string& path); + + // Used as a storage for work file fd and path string. + UniqueFile unique_file_; +}; + +} // namespace installd +} // namespace android + +#endif // ANDROID_INSTALLD_RESTORABLE_FILE_H diff --git a/cmds/installd/tests/Android.bp b/cmds/installd/tests/Android.bp index 4cde7e3fb7..51f7716d3a 100644 --- a/cmds/installd/tests/Android.bp +++ b/cmds/installd/tests/Android.bp @@ -188,3 +188,23 @@ cc_test { "libotapreoptparameters", ], } + +cc_test { + name: "installd_file_test", + test_suites: ["device-tests"], + clang: true, + srcs: ["installd_file_test.cpp"], + cflags: [ + "-Wall", + "-Werror", + ], + shared_libs: [ + "libbase", + "libcutils", + "libutils", + ], + static_libs: [ + "libinstalld", + "liblog", + ], +} diff --git a/cmds/installd/tests/installd_file_test.cpp b/cmds/installd/tests/installd_file_test.cpp new file mode 100644 index 0000000000..00fb30875f --- /dev/null +++ b/cmds/installd/tests/installd_file_test.cpp @@ -0,0 +1,521 @@ +/* + * Copyright (C) 2021 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. + */ + +#include <android-base/file.h> +#include <android-base/logging.h> +#include <android-base/stringprintf.h> +#include <gtest/gtest.h> +#include <log/log.h> +#include <stdlib.h> +#include <string.h> + +#include "restorable_file.h" +#include "unique_file.h" +#include "utils.h" + +#undef LOG_TAG +#define LOG_TAG "installd_file_test" + +namespace { + +constexpr char kFileTestDir[] = "/data/local/tmp/installd_file_test_data"; +constexpr char kTmpFileSuffix[] = ".tmp"; +constexpr char kBackupFileSuffix[] = ".backup"; + +void UnlinkWithAssert(const std::string& path) { + ASSERT_EQ(0, unlink(path.c_str())); +} + +} // namespace + +namespace android { +namespace installd { + +// Add these as macros as functions make it hard to tell where the failure has happened. +#define ASSERT_FILE_NOT_EXISTING(path) \ + { \ + struct stat st; \ + ASSERT_NE(0, ::stat(path.c_str(), &st)); \ + } +#define ASSERT_FILE_EXISTING(path) \ + { \ + struct stat st; \ + ASSERT_EQ(0, ::stat(path.c_str(), &st)); \ + } +#define ASSERT_FILE_CONTENT(path, expectedContent) ASSERT_EQ(expectedContent, ReadTestFile(path)) +#define ASSERT_FILE_OPEN(path, fd) \ + { \ + fd = open(path.c_str(), O_RDWR); \ + ASSERT_TRUE(fd >= 0); \ + } +#define ASSERT_WRITE_TO_FD(fd, content) \ + ASSERT_TRUE(android::base::WriteStringToFd(content, android::base::borrowed_fd(fd))) + +class FileTest : public testing::Test { +protected: + virtual void SetUp() { + setenv("ANDROID_LOG_TAGS", "*:v", 1); + android::base::InitLogging(nullptr); + + ASSERT_EQ(0, create_dir_if_needed(kFileTestDir, 0777)); + } + + virtual void TearDown() { + system(android::base::StringPrintf("rm -rf %s", kFileTestDir).c_str()); + } + + std::string GetTestFilePath(const std::string& fileName) { + return android::base::StringPrintf("%s/%s", kFileTestDir, fileName.c_str()); + } + + void CreateTestFileWithContents(const std::string& path, const std::string& content) { + ALOGI("CreateTestFileWithContents:%s", path.c_str()); + ASSERT_TRUE(android::base::WriteStringToFile(content, path)); + } + + std::string GetTestName() { + std::string name(testing::UnitTest::GetInstance()->current_test_info()->name()); + return name; + } + + std::string ReadTestFile(const std::string& path) { + std::string content; + bool r = android::base::ReadFileToString(path, &content); + if (!r) { + PLOG(ERROR) << "Cannot read file:" << path; + } + return content; + } +}; + +TEST_F(FileTest, TestUniqueFileMoveConstruction) { + const int fd = 101; + std::string testFile = GetTestFilePath(GetTestName()); + UniqueFile uf1(fd, testFile); + uf1.DisableAutoClose(); + + UniqueFile uf2(std::move(uf1)); + + ASSERT_EQ(fd, uf2.fd()); + ASSERT_EQ(testFile, uf2.path()); +} + +TEST_F(FileTest, TestUniqueFileAssignment) { + const int fd1 = 101; + const int fd2 = 102; + std::string testFile1 = GetTestFilePath(GetTestName()); + std::string testFile2 = GetTestFilePath(GetTestName() + "2"); + + UniqueFile uf1(fd1, testFile1); + uf1.DisableAutoClose(); + + UniqueFile uf2(fd2, testFile2); + uf2.DisableAutoClose(); + + ASSERT_EQ(fd2, uf2.fd()); + ASSERT_EQ(testFile2, uf2.path()); + + uf2 = std::move(uf1); + + ASSERT_EQ(fd1, uf2.fd()); + ASSERT_EQ(testFile1, uf2.path()); +} + +TEST_F(FileTest, TestUniqueFileCleanup) { + std::string testFile = GetTestFilePath(GetTestName()); + CreateTestFileWithContents(testFile, "OriginalContent"); + + int fd; + ASSERT_FILE_OPEN(testFile, fd); + + { UniqueFile uf = UniqueFile(fd, testFile, UnlinkWithAssert); } + + ASSERT_FILE_NOT_EXISTING(testFile); +} + +TEST_F(FileTest, TestUniqueFileNoCleanup) { + std::string testFile = GetTestFilePath(GetTestName()); + CreateTestFileWithContents(testFile, "OriginalContent"); + + int fd; + ASSERT_FILE_OPEN(testFile, fd); + + { + UniqueFile uf = UniqueFile(fd, testFile, UnlinkWithAssert); + uf.DisableCleanup(); + } + + ASSERT_FILE_CONTENT(testFile, "OriginalContent"); +} + +TEST_F(FileTest, TestUniqueFileFd) { + std::string testFile = GetTestFilePath(GetTestName()); + CreateTestFileWithContents(testFile, "OriginalContent"); + + int fd; + ASSERT_FILE_OPEN(testFile, fd); + + UniqueFile uf(fd, testFile, UnlinkWithAssert); + + ASSERT_EQ(fd, uf.fd()); + + uf.reset(); + + ASSERT_EQ(-1, uf.fd()); +} + +TEST_F(FileTest, TestRestorableFileMoveConstruction) { + std::string testFile = GetTestFilePath(GetTestName()); + + RestorableFile rf1 = RestorableFile::CreateWritableFile(testFile, 0600); + int fd = rf1.fd(); + + RestorableFile rf2(std::move(rf1)); + + ASSERT_EQ(fd, rf2.fd()); + ASSERT_EQ(testFile, rf2.path()); +} + +TEST_F(FileTest, TestRestorableFileAssignment) { + std::string testFile1 = GetTestFilePath(GetTestName()); + std::string testFile2 = GetTestFilePath(GetTestName() + "2"); + + RestorableFile rf1 = RestorableFile::CreateWritableFile(testFile1, 0600); + int fd1 = rf1.fd(); + + RestorableFile rf2 = RestorableFile::CreateWritableFile(testFile2, 0600); + int fd2 = rf2.fd(); + + ASSERT_EQ(fd2, rf2.fd()); + ASSERT_EQ(testFile2, rf2.path()); + + rf2 = std::move(rf1); + + ASSERT_EQ(fd1, rf2.fd()); + ASSERT_EQ(testFile1, rf2.path()); +} + +TEST_F(FileTest, TestRestorableFileVerifyUniqueFileWithReset) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + + ASSERT_FILE_EXISTING(tmpFile); + + const UniqueFile& uf = rf.GetUniqueFile(); + + ASSERT_EQ(rf.fd(), uf.fd()); + ASSERT_EQ(rf.path(), uf.path()); + + rf.reset(); + + ASSERT_EQ(rf.fd(), uf.fd()); + ASSERT_EQ(rf.path(), uf.path()); + ASSERT_EQ(-1, rf.fd()); + ASSERT_TRUE(rf.path().empty()); + } +} + +TEST_F(FileTest, TestRestorableFileVerifyUniqueFileWithCommit) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + + ASSERT_FILE_EXISTING(tmpFile); + + const UniqueFile& uf = rf.GetUniqueFile(); + + ASSERT_EQ(rf.fd(), uf.fd()); + ASSERT_EQ(rf.path(), uf.path()); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(backupFile); + + rf.CommitWorkFile(); + + ASSERT_EQ(rf.fd(), uf.fd()); + ASSERT_EQ(rf.path(), uf.path()); + ASSERT_EQ(-1, rf.fd()); + ASSERT_EQ(testFile, rf.path()); + } +} + +TEST_F(FileTest, TestRestorableFileNewFileNotCommitted) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + + ASSERT_FILE_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); +} + +TEST_F(FileTest, TestRestorableFileNotCommittedWithOriginal) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + ASSERT_FILE_EXISTING(testFile); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_CONTENT(testFile, "OriginalContent"); +} + +TEST_F(FileTest, TestRestorableFileNotCommittedWithOriginalAndOldTmp) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + CreateTestFileWithContents(testFile + kTmpFileSuffix, "OldTmp"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + ASSERT_FILE_EXISTING(testFile); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_CONTENT(testFile, "OriginalContent"); +} + +TEST_F(FileTest, TestRestorableFileNewFileCommitted) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + + ASSERT_FILE_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(backupFile); + + ASSERT_TRUE(rf.CommitWorkFile()); + rf.RemoveBackupFile(); + + ASSERT_FILE_CONTENT(testFile, "NewContent"); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(backupFile); + ASSERT_FILE_CONTENT(testFile, "NewContent"); +} + +TEST_F(FileTest, TestRestorableFileCommittedWithOriginal) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_EXISTING(backupFile); + + ASSERT_TRUE(rf.CommitWorkFile()); + + ASSERT_FILE_EXISTING(backupFile); + ASSERT_FILE_CONTENT(testFile, "NewContent"); + + rf.RemoveBackupFile(); + + ASSERT_FILE_NOT_EXISTING(backupFile); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_CONTENT(testFile, "NewContent"); +} + +TEST_F(FileTest, TestRestorableFileCommittedWithOriginalAndOldTmp) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + CreateTestFileWithContents(testFile + kTmpFileSuffix, "OldTmp"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + ASSERT_FILE_CONTENT(tmpFile, "NewContent"); + + ASSERT_TRUE(rf.CommitWorkFile()); + + ASSERT_FILE_CONTENT(testFile, "NewContent"); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_CONTENT(testFile, "NewContent"); +} + +TEST_F(FileTest, TestRestorableFileCommitFailureNoOriginal) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(backupFile); + + // Now remove tmp file to force commit failure. + close(rf.fd()); + ASSERT_EQ(0, unlink(tmpFile.c_str())); + ASSERT_FILE_NOT_EXISTING(tmpFile); + + ASSERT_FALSE(rf.CommitWorkFile()); + + ASSERT_EQ(-1, rf.fd()); + ASSERT_EQ(testFile, rf.path()); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(tmpFile); + + ASSERT_TRUE(rf.RestoreBackupFile()); + } + + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(backupFile); +} + +TEST_F(FileTest, TestRestorableFileCommitFailureAndRollback) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_EXISTING(backupFile); + + // Now remove tmp file to force commit failure. + close(rf.fd()); + ASSERT_EQ(0, unlink(tmpFile.c_str())); + ASSERT_FILE_NOT_EXISTING(tmpFile); + + ASSERT_FALSE(rf.CommitWorkFile()); + + ASSERT_EQ(-1, rf.fd()); + ASSERT_EQ(testFile, rf.path()); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_EXISTING(backupFile); + + ASSERT_TRUE(rf.RestoreBackupFile()); + } + + ASSERT_FILE_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(backupFile); +} + +TEST_F(FileTest, TestRestorableFileResetAndRemoveAllFiles) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + + { + RestorableFile rf = RestorableFile::CreateWritableFile(testFile, 0600); + ASSERT_WRITE_TO_FD(rf.fd(), "NewContent"); + + ASSERT_TRUE(rf.CreateBackupFile()); + + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_EXISTING(backupFile); + + rf.ResetAndRemoveAllFiles(); + + ASSERT_EQ(-1, rf.fd()); + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(backupFile); + } + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(backupFile); +} + +TEST_F(FileTest, TestRestorableFileRemoveFileAndTmpFileWithContentFile) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + + RestorableFile::RemoveAllFiles(testFile); + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(backupFile); +} + +TEST_F(FileTest, TestRestorableFileRemoveFileAndTmpFileWithContentAndTmpFile) { + std::string testFile = GetTestFilePath(GetTestName()); + std::string tmpFile = testFile + kTmpFileSuffix; + std::string backupFile = testFile + kBackupFileSuffix; + CreateTestFileWithContents(testFile, "OriginalContent"); + CreateTestFileWithContents(testFile + kTmpFileSuffix, "TmpContent"); + + RestorableFile::RemoveAllFiles(testFile); + + ASSERT_FILE_NOT_EXISTING(tmpFile); + ASSERT_FILE_NOT_EXISTING(testFile); + ASSERT_FILE_NOT_EXISTING(backupFile); +} + +} // namespace installd +} // namespace android diff --git a/cmds/installd/tests/installd_file_test.xml b/cmds/installd/tests/installd_file_test.xml new file mode 100644 index 0000000000..5ec6e3f019 --- /dev/null +++ b/cmds/installd/tests/installd_file_test.xml @@ -0,0 +1,35 @@ +<?xml version="1.0" encoding="utf-8"?> +<!-- Copyright (C) 2021 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. +--> +<!-- Note: this is derived from the autogenerated configuration. We require + root support. --> +<configuration description="Runs installd_file_test."> + <option name="test-suite-tag" value="apct" /> + <option name="test-suite-tag" value="apct-native" /> + + <target_preparer class="com.android.tradefed.targetprep.PushFilePreparer"> + <option name="cleanup" value="true" /> + <option name="push" + value="installd_file_test->/data/local/tmp/installd_file_test" /> + </target_preparer> + + <!-- The test requires root for file access (rollback. --> + <target_preparer class="com.android.tradefed.targetprep.RootTargetPreparer" /> + + <test class="com.android.tradefed.testtype.GTest" > + <option name="native-test-device-path" value="/data/local/tmp" /> + <option name="module-name" value="installd_file_test" /> + </test> +</configuration> |