Add flock(2)ing on dex-cache files to prevent races

Bug: 9071417
Change-Id: I1ee9ff281867f90fba7a8ed8bbf06b33ac29d511
diff --git a/runtime/class_linker.cc b/runtime/class_linker.cc
index bd36a6c..039e7bc 100644
--- a/runtime/class_linker.cc
+++ b/runtime/class_linker.cc
@@ -659,6 +659,7 @@
       CHECK_NE(&oat_file, oat_files_[i]) << oat_file.GetLocation();
     }
   }
+  VLOG(class_linker) << "Registering " << oat_file.GetLocation();
   oat_files_.push_back(&oat_file);
 }
 
@@ -694,22 +695,39 @@
   UniquePtr<OatFile> oat_file(OatFile::Open(oat_location, oat_location, NULL,
                                             !Runtime::Current()->IsCompiler()));
   if (oat_file.get() == NULL) {
+    VLOG(class_linker) << "Failed to find existing oat file at " << oat_location;
     return NULL;
   }
   Runtime* runtime = Runtime::Current();
   const ImageHeader& image_header = runtime->GetHeap()->GetImageSpace()->GetImageHeader();
-  if (oat_file->GetOatHeader().GetImageFileLocationOatChecksum() != image_header.GetOatChecksum()) {
+  uint32_t expected_image_oat_checksum = image_header.GetOatChecksum();
+  uint32_t actual_image_oat_checksum = oat_file->GetOatHeader().GetImageFileLocationOatChecksum();
+  if (expected_image_oat_checksum != actual_image_oat_checksum) {
+    VLOG(class_linker) << "Failed to find oat file at " << oat_location
+                       << " with expected image oat checksum of " << expected_image_oat_checksum
+                       << ", found " << actual_image_oat_checksum;
     return NULL;
   }
-  if (oat_file->GetOatHeader().GetImageFileLocationOatDataBegin()
-      != reinterpret_cast<uint32_t>(image_header.GetOatDataBegin())) {
+
+  uint32_t expected_image_oat_offset = reinterpret_cast<uint32_t>(image_header.GetOatDataBegin());
+  uint32_t actual_image_oat_offset = oat_file->GetOatHeader().GetImageFileLocationOatDataBegin();
+  if (expected_image_oat_offset != actual_image_oat_offset) {
+    VLOG(class_linker) << "Failed to find oat file at " << oat_location
+                       << " with expected image oat offset " << expected_image_oat_offset
+                       << ", found " << actual_image_oat_offset;
     return NULL;
   }
   const OatFile::OatDexFile* oat_dex_file = oat_file->GetOatDexFile(dex_location);
   if (oat_dex_file == NULL) {
+    VLOG(class_linker) << "Failed to find oat file at " << oat_location << " containing " << dex_location;
     return NULL;
   }
-  if (oat_dex_file->GetDexFileLocationChecksum() != dex_location_checksum) {
+  uint32_t expected_dex_checksum = dex_location_checksum;
+  uint32_t actual_dex_checksum = oat_dex_file->GetDexFileLocationChecksum();
+  if (expected_dex_checksum != actual_dex_checksum) {
+    VLOG(class_linker) << "Failed to find oat file at " << oat_location
+                       << " with expected dex checksum of " << expected_dex_checksum
+                       << ", found " << actual_dex_checksum;
     return NULL;
   }
   RegisterOatFileLocked(*oat_file.release());
@@ -722,6 +740,58 @@
   return FindOrCreateOatFileForDexLocationLocked(dex_location, oat_location);
 }
 
+class ScopedFlock {
+ public:
+  ScopedFlock() {}
+
+  bool Init(const std::string& filename) {
+    while (true) {
+      file_.reset(OS::OpenFileWithFlags(filename.c_str(), O_CREAT | O_RDWR));
+      if (file_.get() == NULL) {
+        LOG(ERROR) << "Failed to open file: " << filename;
+        return false;
+      }
+      int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_EX));
+      if (flock_result != 0) {
+        PLOG(ERROR) << "Failed to lock file: " << filename;
+        return false;
+      }
+      struct stat fstat_stat;
+      int fstat_result = TEMP_FAILURE_RETRY(fstat(file_->Fd(), &fstat_stat));
+      if (fstat_result != 0) {
+        PLOG(ERROR) << "Failed to fstat: " << filename;
+        return false;
+      }
+      struct stat stat_stat;
+      int stat_result = TEMP_FAILURE_RETRY(stat(filename.c_str(), &stat_stat));
+      if (stat_result != 0) {
+        PLOG(WARNING) << "Failed to stat, will retry: " << filename;
+        // ENOENT can happen if someone racing with us unlinks the file we created so just retry.
+        continue;
+      }
+      if (fstat_stat.st_dev != stat_stat.st_dev || fstat_stat.st_ino != stat_stat.st_ino) {
+        LOG(WARNING) << "File changed while locking, will retry: " << filename;
+        continue;
+      }
+      return true;
+    }
+  }
+
+  File& GetFile() {
+    return *file_;
+  }
+
+  ~ScopedFlock() {
+    int flock_result = TEMP_FAILURE_RETRY(flock(file_->Fd(), LOCK_UN));
+    CHECK_EQ(0, flock_result);
+  }
+
+ private:
+  UniquePtr<File> file_;
+
+  DISALLOW_COPY_AND_ASSIGN(ScopedFlock);
+};
+
 const DexFile* ClassLinker::FindOrCreateOatFileForDexLocationLocked(const std::string& dex_location,
                                                                     const std::string& oat_location) {
   uint32_t dex_location_checksum;
@@ -730,6 +800,17 @@
     return NULL;
   }
 
+  // We play a locking game here so that if two different processes
+  // race to generate (or worse, one tries to open a partial generated
+  // file) we will be okay. This is actually common with apps that use
+  // DexClassLoader to work around the dex method reference limit and
+  // that have a background service running in a separate process.
+  ScopedFlock scoped_flock;
+  if (!scoped_flock.Init(oat_location)) {
+    LOG(ERROR) << "Failed to open locked oat file: " << oat_location;
+    return NULL;
+  }
+
   // Check if we already have an up-to-date output file
   const DexFile* dex_file = FindDexFileInOatLocation(dex_location,
                                                      dex_location_checksum,
@@ -739,12 +820,8 @@
   }
 
   // Generate the output oat file for the dex file
-  UniquePtr<File> file(OS::OpenFile(oat_location.c_str(), true));
-  if (file.get() == NULL) {
-    LOG(ERROR) << "Failed to create oat file: " << oat_location;
-    return NULL;
-  }
-  if (!GenerateOatFile(dex_location, file->Fd(), oat_location)) {
+  VLOG(class_linker) << "Generating oat file " << oat_location << " for " << dex_location;
+  if (!GenerateOatFile(dex_location, scoped_flock.GetFile().Fd(), oat_location)) {
     LOG(ERROR) << "Failed to generate oat file: " << oat_location;
     return NULL;
   }