Invalidate FUSE VFS dentry cache

To improve filesystem performance we can reduce unnecessary
requests to the FUSE daemon by enabling VFS caching of dentries
after a FUSE_LOOKUP request.

To enable correctly, we should ensure that the lower filesystem
dentries are not modified outside of the FUSE driver. Unfortunately,
we already do this when handling IO requests over binder using the
ContentResolver interface.

To fix, we should invalidate any FUSE dentries in the VFS cache so that
subsequent lookups for those files do not return incorrectly cached
values from the VFS.

This change just adds support for invalidating FUSE VFS cache
dentries, subsequent cl will call it when required. Note that it
should never be called in the execution path of a related filesytem
operation. See fuse_lowlevel.h for more details. Will ensure that this
is never called in the FUSE execution path in a follow up

Test: atest FuseDaemonHostTest#testVfsCacheConsistency
Bug: 145741152
Change-Id: I54e67f63a29c82a392916fb28d1a7f8f894c685b
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 282a088..d8c2f46 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -299,6 +299,7 @@
     std::recursive_mutex lock;
     const string path;
     node* const root;
+    struct fuse_session* se;
 
     /*
      * Used to make JNI calls to MediaProvider.
@@ -1540,6 +1541,31 @@
     return use_fuse;
 }
 
+void FuseDaemon::InvalidateFuseDentryCache(const std::string& path) {
+    TRACE_VERBOSE << "Invalidating dentry for path " << path;
+
+    if (active.load(std::memory_order_acquire)) {
+        string name;
+        fuse_ino_t parent;
+
+        {
+            std::lock_guard<std::recursive_mutex> guard(fuse->lock);
+            const node* node = node::LookupAbsolutePath(fuse->root, path);
+            if (node) {
+                name = node->GetName();
+                parent = fuse->ToInode(node->GetParent());
+            }
+        }
+
+        if (!name.empty() &&
+            fuse_lowlevel_notify_inval_entry(fuse->se, parent, name.c_str(), name.size())) {
+            LOG(ERROR) << "Failed to invalidate dentry for path " << path;
+        }
+    } else {
+        TRACE << "FUSE daemon is inactive. Cannot invalidate dentry for " << path;
+    }
+}
+
 FuseDaemon::FuseDaemon(JNIEnv* env, jobject mediaProvider) : mp(env, mediaProvider),
                                                              active(false), fuse(nullptr) {}
 
@@ -1594,6 +1620,7 @@
         PLOG(ERROR) << "Failed to create session ";
         return;
     }
+    fuse_default.se = se;
     se->fd = fd;
     se->mountpoint = strdup(path.c_str());
 
diff --git a/jni/FuseDaemon.h b/jni/FuseDaemon.h
index 1233863..1d25636 100644
--- a/jni/FuseDaemon.h
+++ b/jni/FuseDaemon.h
@@ -42,6 +42,11 @@
      */
     bool ShouldOpenWithFuse(int fd, bool for_read, const std::string& path);
 
+    /**
+     * Invalidate FUSE VFS dentry cache entry for path
+     */
+    void InvalidateFuseDentryCache(const std::string& path);
+
   private:
     FuseDaemon(const FuseDaemon&) = delete;
     void operator=(const FuseDaemon&) = delete;
diff --git a/jni/com_android_providers_media_FuseDaemon.cpp b/jni/com_android_providers_media_FuseDaemon.cpp
index caf00b2..87861ca 100644
--- a/jni/com_android_providers_media_FuseDaemon.cpp
+++ b/jni/com_android_providers_media_FuseDaemon.cpp
@@ -73,6 +73,22 @@
     return JNI_FALSE;
 }
 
+void com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache(JNIEnv* env, jobject self,
+                                                                         jlong java_daemon,
+                                                                         jstring java_path) {
+    fuse::FuseDaemon* const daemon = reinterpret_cast<fuse::FuseDaemon*>(java_daemon);
+    if (daemon) {
+        ScopedUtfChars utf_chars_path(env, java_path);
+        if (!utf_chars_path.c_str()) {
+            // TODO(b/145741152): Throw exception
+            return;
+        }
+
+        daemon->InvalidateFuseDentryCache(utf_chars_path.c_str());
+    }
+    // TODO(b/145741152): Throw exception
+}
+
 const JNINativeMethod methods[] = {
         {"native_new", "(Lcom/android/providers/media/MediaProvider;)J",
          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_new)},
@@ -81,8 +97,10 @@
         {"native_delete", "(J)V",
          reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_delete)},
         {"native_should_open_with_fuse", "(JLjava/lang/String;ZI)Z",
-         reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_should_open_with_fuse)}};
-
+         reinterpret_cast<void*>(com_android_providers_media_FuseDaemon_should_open_with_fuse)},
+        {"native_invalidate_fuse_dentry_cache", "(JLjava/lang/String;)V",
+         reinterpret_cast<void*>(
+                 com_android_providers_media_FuseDaemon_invalidate_fuse_dentry_cache)}};
 }  // namespace
 
 void register_android_providers_media_FuseDaemon(JNIEnv* env) {
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 76d1ba9..e21ae54 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -160,6 +160,11 @@
         return name_;
     }
 
+    node* GetParent() const {
+        std::lock_guard<std::recursive_mutex> guard(*lock_);
+        return parent_;
+    }
+
     inline void AddHandle(handle* h) {
         std::lock_guard<std::recursive_mutex> guard(*lock_);
         handles_.emplace_back(std::unique_ptr<handle>(h));
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 57ac286..c109cae 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -4758,6 +4758,14 @@
         return new File(filePath);
     }
 
+    private FuseDaemon getFuseDaemonForFile(File file) {
+        StorageVolume volume = mStorageManager.getStorageVolume(file);
+        if (volume == null) {
+            return null;
+        }
+        return ExternalStorageServiceImpl.getFuseDaemon(volume.getId());
+    }
+
     /**
      * Replacement for {@link #openFileHelper(Uri, String)} which enforces any
      * permissions applicable to the path before returning.
@@ -4865,12 +4873,7 @@
                             redactionInfo.freeOffsets);
                 }
             } else {
-                FuseDaemon daemon = null;
-
-                StorageVolume volume = mStorageManager.getStorageVolume(file);
-                if (volume != null) {
-                    daemon = ExternalStorageServiceImpl.getFuseDaemon(volume.getId());
-                }
+                FuseDaemon daemon = getFuseDaemonForFile(file);
                 ParcelFileDescriptor lowerFsFd = ParcelFileDescriptor.open(file, modeBits);
                 boolean forRead = (modeBits & ParcelFileDescriptor.MODE_READ_ONLY) != 0;
                 boolean shouldOpenWithFuse = daemon != null
diff --git a/src/com/android/providers/media/fuse/FuseDaemon.java b/src/com/android/providers/media/fuse/FuseDaemon.java
index 19ebc03..3ce2160 100644
--- a/src/com/android/providers/media/fuse/FuseDaemon.java
+++ b/src/com/android/providers/media/fuse/FuseDaemon.java
@@ -99,9 +99,17 @@
         return native_should_open_with_fuse(mPtr, path, readLock, fd);
     }
 
+    /**
+     * Invalidates FUSE VFS dentry cache for {@code path}
+     */
+    public void invalidateFuseDentryCache(String path) {
+        native_invalidate_fuse_dentry_cache(mPtr, path);
+    }
+
     private native long native_new(MediaProvider mediaProvider);
     private native void native_start(long daemon, int deviceFd, String path);
     private native void native_delete(long daemon);
     private native boolean native_should_open_with_fuse(long daemon, String path, boolean readLock,
             int fd);
+    private native void native_invalidate_fuse_dentry_cache(long daemon, String path);
 }