dexopt: Restore old files when cancelled

- out_oat, out_vdex, out_image will be first updated upon
  temporary work file. When dex2oat is completed, this temp
  file is committed and renamed into regular file.
- For whatever reason, if renaming fails, guarantee three states:
  all files committed to new version
  all files kept with original version
  all three files removed
- reference_profile is kept for cancellation or successful run
  while removed for dexopt failure.
- Added RestorableFile to handle usage for out_* files.
- Added unit test.
- Also fixed wrong returning of completed state for cancellation.

Bug: 203689833
Bug: 32230521
Test: atest installd_unique_file_test installd_dexopt_test
Change-Id: Ib6804fb311596dfd9180c1d6083cd2e9a661b621
diff --git a/cmds/installd/restorable_file.cpp b/cmds/installd/restorable_file.cpp
new file mode 100644
index 0000000..fd54a23
--- /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