Work around O_DIRECT being invalid on Android

If a file is opened with O_DIRECT on Android, an EINVAL will be
triggered.  To try to accomplish the same effect as O_DIRECT,
this change adds calls to flush the page cache at key points for
the file and disk threads.  These calls will sync and write a "1"
to /proc/sys/vm/drop_caches if O_DIRECT caused an EINVAL, but
will be a NOP otherwise.

Change-Id: If0c7ca455384f9d60d4587127e96979a3c7f1169
Signed-off-by: Scott Anderson <saa@android.com>
diff --git a/src/os.cc b/src/os.cc
index 3434baa..8032cfc 100644
--- a/src/os.cc
+++ b/src/os.cc
@@ -77,6 +77,8 @@
 
   has_clflush_ = false;
   has_sse2_ = false;
+
+  use_flush_page_cache_ = false;
 }
 
 // OsLayer cleanup.
@@ -169,6 +171,46 @@
 }
 
 
+// Enable FlushPageCache to be functional instead of a NOP.
+void OsLayer::ActivateFlushPageCache(void) {
+  logprintf(9, "Log: page cache will be flushed as needed\n");
+  use_flush_page_cache_ = true;
+}
+
+// Flush the page cache to ensure reads come from the disk.
+bool OsLayer::FlushPageCache(void) {
+  if (!use_flush_page_cache_)
+    return true;
+
+  // First, ask the kernel to write the cache to the disk.
+  sync();
+
+  // Second, ask the kernel to empty the cache by writing "1" to
+  // "/proc/sys/vm/drop_caches".
+  static const char *drop_caches_file = "/proc/sys/vm/drop_caches";
+  int dcfile = open(drop_caches_file, O_WRONLY);
+  if (dcfile < 0) {
+    int err = errno;
+    string errtxt = ErrorString(err);
+    logprintf(3, "Log: failed to open %s - err %d (%s)\n",
+              drop_caches_file, err, errtxt.c_str());
+    return false;
+  }
+
+  ssize_t bytes_written = write(dcfile, "1", 1);
+  close(dcfile);
+
+  if (bytes_written != 1) {
+    int err = errno;
+    string errtxt = ErrorString(err);
+    logprintf(3, "Log: failed to write %s - err %d (%s)\n",
+              drop_caches_file, err, errtxt.c_str());
+    return false;
+  }
+  return true;
+}
+
+
 // We need to flush the cacheline here.
 void OsLayer::Flush(void *vaddr) {
   // Use the generic flush. This function is just so we can override
diff --git a/src/os.h b/src/os.h
index 28c8a2a..b043b61 100644
--- a/src/os.h
+++ b/src/os.h
@@ -101,6 +101,15 @@
   // This will output a machine readable line regarding the error.
   virtual bool ErrorReport(const char *part, const char *symptom, int count);
 
+  // Flushes page cache. Used to circumvent the page cache when doing disk
+  // I/O.  This will be a NOP until ActivateFlushPageCache() is called, which
+  // is typically done when opening a file with O_DIRECT fails.
+  // Returns false on error, true on success or NOP.
+  // Subclasses may implement this in machine specific ways..
+  virtual bool FlushPageCache(void);
+  // Enable FlushPageCache() to actually do the flush instead of being a NOP.
+  virtual void ActivateFlushPageCache(void);
+
   // Flushes cacheline. Used to distinguish read or write errors.
   // Subclasses may implement this in machine specific ways..
   // Takes a pointer, and flushed the cacheline containing that pointer.
@@ -260,6 +269,7 @@
   int   address_mode_;           // Are we running 32 or 64 bit?
   bool  has_sse2_;               // Do we have sse2 instructions?
   bool  has_clflush_;            // Do we have clflush instructions?
+  bool  use_flush_page_cache_;   // Do we need to flush the page cache?
 
 
   time_t time_initialized_;      // Start time of test.
diff --git a/src/worker.cc b/src/worker.cc
index 2dae464..dcf4dcb 100644
--- a/src/worker.cc
+++ b/src/worker.cc
@@ -1600,15 +1600,21 @@
 
 // Open the file for access.
 bool FileThread::OpenFile(int *pfile) {
-  int fd = open(filename_.c_str(),
-                O_RDWR | O_CREAT | O_SYNC | O_DIRECT,
-                0644);
+  bool no_O_DIRECT = false;
+  int flags = O_RDWR | O_CREAT | O_SYNC;
+  int fd = open(filename_.c_str(), flags | O_DIRECT, 0644);
+  if (O_DIRECT != 0 && fd < 0 && errno == EINVAL) {
+    no_O_DIRECT = true;
+    fd = open(filename_.c_str(), flags, 0644); // Try without O_DIRECT
+  }
   if (fd < 0) {
     logprintf(0, "Process Error: Failed to create file %s!!\n",
               filename_.c_str());
     pages_copied_ = 0;
     return false;
   }
+  if (no_O_DIRECT)
+    os_->ActivateFlushPageCache(); // Not using O_DIRECT fixed EINVAL
   *pfile = fd;
   return true;
 }
@@ -1679,7 +1685,7 @@
     if (!result)
       return false;
   }
-  return true;
+  return os_->FlushPageCache(); // If O_DIRECT worked, this will be a NOP.
 }
 
 // Copy data from file into memory block.
@@ -2691,14 +2697,20 @@
 
 // Open a device, return false on failure.
 bool DiskThread::OpenDevice(int *pfile) {
-  int fd = open(device_name_.c_str(),
-                O_RDWR | O_SYNC | O_DIRECT | O_LARGEFILE,
-                0);
+  bool no_O_DIRECT = false;
+  int flags = O_RDWR | O_SYNC | O_LARGEFILE;
+  int fd = open(device_name_.c_str(), flags | O_DIRECT, 0);
+  if (O_DIRECT != 0 && fd < 0 && errno == EINVAL) {
+    no_O_DIRECT = true;
+    fd = open(device_name_.c_str(), flags, 0); // Try without O_DIRECT
+  }
   if (fd < 0) {
     logprintf(0, "Process Error: Failed to open device %s (thread %d)!!\n",
               device_name_.c_str(), thread_num_);
     return false;
   }
+  if (no_O_DIRECT)
+    os_->ActivateFlushPageCache();
   *pfile = fd;
 
   return GetDiskSize(fd);
@@ -2858,6 +2870,8 @@
 
       in_flight_sectors_.push(block);
     }
+    if (!os_->FlushPageCache()) // If O_DIRECT worked, this will be a NOP.
+      return false;
 
     // Verify blocks on disk.
     logprintf(20, "Log: Read phase for disk %s (thread %d).\n",