Snap for 6755001 from 22613b7917e90d3f75cbd107bc49f82895381388 to rvc-qpr1-release

Change-Id: I49aa288d6b8f382e37f17f07b18d023324cc0b19
diff --git a/apexd/apexd_loop.cpp b/apexd/apexd_loop.cpp
index 568eb05..5ca096b 100644
--- a/apexd/apexd_loop.cpp
+++ b/apexd/apexd_loop.cpp
@@ -18,6 +18,8 @@
 
 #include "apexd_loop.h"
 
+#include <mutex>
+
 #include <dirent.h>
 #include <fcntl.h>
 #include <linux/fs.h>
@@ -41,6 +43,17 @@
 using android::base::StringPrintf;
 using android::base::unique_fd;
 
+#ifndef LOOP_CONFIGURE
+// These can be removed whenever we pull in the Linux v5.8 UAPI headers
+struct loop_config {
+  __u32 fd;
+  __u32 block_size;
+  struct loop_info64 info;
+  __u64 __reserved[8];
+};
+#define LOOP_CONFIGURE 0x4C0A
+#endif
+
 namespace android {
 namespace apex {
 namespace loop {
@@ -125,6 +138,99 @@
   return {};
 }
 
+Result<void> configureLoopDevice(const int device_fd, const std::string& target,
+                                 const int32_t imageOffset,
+                                 const size_t imageSize) {
+  static bool useLoopConfigure;
+  static std::once_flag onceFlag;
+  std::call_once(onceFlag, [&]() {
+    // LOOP_CONFIGURE is a new ioctl in Linux 5.8 (and backported in Android
+    // common) that allows atomically configuring a loop device. It is a lot
+    // faster than the traditional LOOP_SET_FD/LOOP_SET_STATUS64 combo, but
+    // it may not be available on updating devices, so try once before
+    // deciding.
+    struct loop_config config;
+    memset(&config, 0, sizeof(config));
+    config.fd = -1;
+    if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1 && errno == EBADF) {
+      // If the IOCTL exists, it will fail with EBADF for the -1 fd
+      useLoopConfigure = true;
+    }
+  });
+
+  /*
+   * Using O_DIRECT will tell the kernel that we want to use Direct I/O
+   * on the underlying file, which we want to do to avoid double caching.
+   * Note that Direct I/O won't be enabled immediately, because the block
+   * size of the underlying block device may not match the default loop
+   * device block size (512); when we call LOOP_SET_BLOCK_SIZE below, the
+   * kernel driver will automatically enable Direct I/O when it sees that
+   * condition is now met.
+   */
+  unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
+  if (target_fd.get() == -1) {
+    return ErrnoError() << "Failed to open " << target;
+  }
+
+  struct loop_info64 li;
+  memset(&li, 0, sizeof(li));
+  strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
+  li.lo_offset = imageOffset;
+  li.lo_sizelimit = imageSize;
+
+  if (useLoopConfigure) {
+    struct loop_config config;
+    memset(&config, 0, sizeof(config));
+    li.lo_flags |= LO_FLAGS_DIRECT_IO;
+    config.fd = target_fd.get();
+    config.info = li;
+    config.block_size = 4096;
+
+    if (ioctl(device_fd, LOOP_CONFIGURE, &config) == -1) {
+      return ErrnoError() << "Failed to LOOP_CONFIGURE";
+    }
+
+    return {};
+  } else {
+    if (ioctl(device_fd, LOOP_SET_FD, target_fd.get()) == -1) {
+      return ErrnoError() << "Failed to LOOP_SET_FD";
+    }
+
+    if (ioctl(device_fd, LOOP_SET_STATUS64, &li) == -1) {
+      return ErrnoError() << "Failed to LOOP_SET_STATUS64";
+    }
+
+    if (ioctl(device_fd, BLKFLSBUF, 0) == -1) {
+      // This works around a kernel bug where the following happens.
+      // 1) The device runs with a value of loop.max_part > 0
+      // 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
+      //    the first 2 pages of the underlying file into the buffer cache
+      // 3) When we then change the offset with LOOP_SET_STATUS64, those pages
+      //    are not invalidated from the cache.
+      // 4) When we try to mount an ext4 filesystem on the loop device, the ext4
+      //    code will try to find a superblock by reading 4k at offset 0; but,
+      //    because we still have the old pages at offset 0 lying in the cache,
+      //    those pages will be returned directly. However, those pages contain
+      //    the data at offset 0 in the underlying file, not at the offset that
+      //    we configured
+      // 5) the ext4 driver fails to find a superblock in the (wrong) data, and
+      //    fails to mount the filesystem.
+      //
+      // To work around this, explicitly flush the block device, which will
+      // flush the buffer cache and make sure we actually read the data at the
+      // correct offset.
+      return ErrnoError() << "Failed to flush buffers on the loop device";
+    }
+
+    // Direct-IO requires the loop device to have the same block size as the
+    // underlying filesystem.
+    if (ioctl(device_fd, LOOP_SET_BLOCK_SIZE, 4096) == -1) {
+      PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
+    }
+  }
+  return {};
+}
+
 Result<LoopbackDeviceUniqueFd> createLoopDevice(const std::string& target,
                                                 const int32_t imageOffset,
                                                 const size_t imageSize) {
@@ -140,19 +246,6 @@
 
   std::string device = StringPrintf("/dev/block/loop%d", num);
 
-  /*
-   * Using O_DIRECT will tell the kernel that we want to use Direct I/O
-   * on the underlying file, which we want to do to avoid double caching.
-   * Note that Direct I/O won't be enabled immediately, because the block
-   * size of the underlying block device may not match the default loop
-   * device block size (512); when we call LOOP_SET_BLOCK_SIZE below, the
-   * kernel driver will automatically enable Direct I/O when it sees that
-   * condition is now met.
-   */
-  unique_fd target_fd(open(target.c_str(), O_RDONLY | O_CLOEXEC | O_DIRECT));
-  if (target_fd.get() == -1) {
-    return ErrnoError() << "Failed to open " << target;
-  }
   LoopbackDeviceUniqueFd device_fd;
   {
     // See comment on kLoopDeviceRetryAttempts.
@@ -173,45 +266,10 @@
     CHECK_NE(device_fd.get(), -1);
   }
 
-  if (ioctl(device_fd.get(), LOOP_SET_FD, target_fd.get()) == -1) {
-    return ErrnoError() << "Failed to LOOP_SET_FD";
-  }
-
-  struct loop_info64 li;
-  memset(&li, 0, sizeof(li));
-  strlcpy((char*)li.lo_crypt_name, kApexLoopIdPrefix, LO_NAME_SIZE);
-  li.lo_offset = imageOffset;
-  li.lo_sizelimit = imageSize;
-  if (ioctl(device_fd.get(), LOOP_SET_STATUS64, &li) == -1) {
-    return ErrnoError() << "Failed to LOOP_SET_STATUS64";
-  }
-
-  if (ioctl(device_fd.get(), BLKFLSBUF, 0) == -1) {
-    // This works around a kernel bug where the following happens.
-    // 1) The device runs with a value of loop.max_part > 0
-    // 2) As part of LOOP_SET_FD above, we do a partition scan, which loads
-    //    the first 2 pages of the underlying file into the buffer cache
-    // 3) When we then change the offset with LOOP_SET_STATUS64, those pages
-    //    are not invalidated from the cache.
-    // 4) When we try to mount an ext4 filesystem on the loop device, the ext4
-    //    code will try to find a superblock by reading 4k at offset 0; but,
-    //    because we still have the old pages at offset 0 lying in the cache,
-    //    those pages will be returned directly. However, those pages contain
-    //    the data at offset 0 in the underlying file, not at the offset that
-    //    we configured
-    // 5) the ext4 driver fails to find a superblock in the (wrong) data, and
-    //    fails to mount the filesystem.
-    //
-    // To work around this, explicitly flush the block device, which will flush
-    // the buffer cache and make sure we actually read the data at the correct
-    // offset.
-    return ErrnoError() << "Failed to flush buffers on the loop device";
-  }
-
-  // Direct-IO requires the loop device to have the same block size as the
-  // underlying filesystem.
-  if (ioctl(device_fd.get(), LOOP_SET_BLOCK_SIZE, 4096) == -1) {
-    PLOG(WARNING) << "Failed to LOOP_SET_BLOCK_SIZE";
+  Result<void> configureStatus =
+      configureLoopDevice(device_fd.get(), target, imageOffset, imageSize);
+  if (!configureStatus.ok()) {
+    return configureStatus.error();
   }
 
   Result<void> readAheadStatus = configureReadAhead(device);