Handle userfaultfd API ioctl on older kernel hosts

On older kernels on host it's possible that minor-fault feature doesn't
exist, in which case the ioctl may fail with EINVAL. This CL detects the
failure reason and retries the ioctl without it.

Bug: 160737021
Test: art/test/testrunner/testrunner.py --host
Change-Id: Ibd55933ec985f5e62fdaa9dd85ed57d544546d18
diff --git a/runtime/gc/collector/mark_compact.cc b/runtime/gc/collector/mark_compact.cc
index e7ca948..464530d 100644
--- a/runtime/gc/collector/mark_compact.cc
+++ b/runtime/gc/collector/mark_compact.cc
@@ -189,11 +189,29 @@
                      << ") and therefore falling back to stop-the-world compaction.";
       } else {
         DCHECK(IsValidFd(uffd_));
+        uint64_t requested_features = UFFD_FEATURE_MISSING_SHMEM | UFFD_FEATURE_MINOR_SHMEM;
         // Get/update the features that we want in userfaultfd
-        struct uffdio_api api = {.api = UFFD_API,
-                                 .features = UFFD_FEATURE_MISSING_SHMEM | UFFD_FEATURE_MINOR_SHMEM};
-        CHECK_EQ(ioctl(uffd_, UFFDIO_API, &api), 0)
-              << "ioctl_userfaultfd: API: " << strerror(errno);
+        struct uffdio_api api = {.api = UFFD_API, .features = requested_features, .ioctls = 0};
+        int ret = ioctl(uffd_, UFFDIO_API, &api);
+        if (UNLIKELY(ret != 0)) {
+          if (errno == EINVAL) {
+            // Confirm that this didn't happen because uffd was already
+            // initialized by a previous invocation of API ioctl. In that case
+            // 'api.ioctls != 0'.
+            CHECK_EQ(api.ioctls, 0u)
+                << "ioctl_userfaultfd: API: " << strerror(errno) << ". uffd already initialized";
+            // This means we are requesting some feature which doesn't exist
+            // on this kernel. This can't be with UFFD_FEATURE_MISSING_SHMEM
+            // as it's one of the earliest features introduced in userfaultfd.
+            //
+            // Invoke the ioctl again, but without UFFD_FEATURE_MINOR_SHMEM.
+            api.features &= ~UFFD_FEATURE_MINOR_SHMEM;
+            CHECK_EQ(ioctl(uffd_, UFFDIO_API, &api), 0)
+                << "ioctl_userfaultfd: API: failed again with: " << strerror(errno);
+          } else {
+            LOG(FATAL) << "ioctl_userfaultfd: API: " << strerror(errno);
+          }
+        }
         // Missing userfaults on shmem should always be available.
         DCHECK_NE(api.features & UFFD_FEATURE_MISSING_SHMEM, 0u);
         uffd_minor_fault_supported_ =