Merge "Replace REGEXP with LIKE for sql queries in renameForFuse" into rvc-qpr-dev
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 942b866..ecb7092 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -427,10 +427,14 @@
// invalidate node_name if different case
// Note that we invalidate async otherwise we will deadlock the kernel
if (name != node->GetName()) {
- std::thread t([=]() {
- fuse_inval(fuse->se, fuse->ToInode(parent), fuse->ToInode(node), node->GetName(),
- path);
- });
+ // Make copies of the node name and path so we're not attempting to acquire
+ // any node locks from the invalidation thread. Depending on timing, we may end
+ // up invalidating the wrong inode but that shouldn't result in correctness issues.
+ const fuse_ino_t parent_ino = fuse->ToInode(parent);
+ const fuse_ino_t child_ino = fuse->ToInode(node);
+ const std::string& node_name = node->GetName();
+
+ std::thread t([=]() { fuse_inval(fuse->se, parent_ino, child_ino, node_name, path); });
t.detach();
}
}
diff --git a/jni/node-inl.h b/jni/node-inl.h
index d6ad0ad..364a327 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -19,12 +19,16 @@
#include <android-base/logging.h>
+#include <cstdint>
+#include <limits>
#include <list>
#include <memory>
#include <mutex>
+#include <set>
#include <sstream>
#include <string>
#include <unordered_set>
+#include <utility>
#include <vector>
#include "libfuse_jni/ReaddirHelper.h"
@@ -175,14 +179,18 @@
node* LookupChildByName(const std::string& name, bool acquire) const {
std::lock_guard<std::recursive_mutex> guard(*lock_);
- const char* name_char = name.c_str();
- for (node* child : children_) {
- const std::string& child_name = child->GetName();
- if (!strcasecmp(name_char, child_name.c_str()) && !child->deleted_) {
+ // lower_bound will give us the first child with strcasecmp(child->name, name) >=0.
+ // For more context see comment on the NodeCompare struct.
+ auto start = children_.lower_bound(std::make_pair(name, 0));
+ // upper_bound will give us the first child with strcasecmp(child->name, name) > 0
+ auto end =
+ children_.upper_bound(std::make_pair(name, std::numeric_limits<uintptr_t>::max()));
+ for (auto it = start; it != end; it++) {
+ node* child = *it;
+ if (!child->deleted_) {
if (acquire) {
child->Acquire();
}
-
return child;
}
}
@@ -201,10 +209,41 @@
void Rename(const std::string& name, node* new_parent) {
std::lock_guard<std::recursive_mutex> guard(*lock_);
- name_ = name;
if (new_parent != parent_) {
RemoveFromParent();
+ name_ = name;
AddToParent(new_parent);
+ return;
+ }
+
+ // Changing name_ will change the expected position of this node in parent's set of
+ // children. Consider following scenario:
+ // 1. This node: "b"; parent's set: {"a", "b", "c"}
+ // 2. Rename("b", "d")
+ //
+ // After rename, parent's set should become: {"a", "b", "d"}, but if we simply change the
+ // name it will be {"a", "d", "b"}, which violates properties of the set.
+ //
+ // To make sure that parent's set is always valid, changing name is 3 steps procedure:
+ // 1. Remove this node from parent's set.
+ // 2 Change the name.
+ // 3. Add it back to the set.
+ // Rename of node without changing its parent. Still need to remove and re-add it to make
+ // sure lookup index is correct.
+ if (name_ != name) {
+ // If this is a root node, simply rename it.
+ if (parent_ == nullptr) {
+ name_ = name;
+ return;
+ }
+
+ auto it = parent_->children_.find(this);
+ CHECK(it != parent_->children_.end());
+ parent_->children_.erase(it);
+
+ name_ = name;
+
+ parent_->children_.insert(this);
}
}
@@ -299,7 +338,7 @@
CHECK(parent != nullptr);
parent_ = parent;
- parent_->children_.push_back(this);
+ parent_->children_.insert(this);
// TODO(narayan, zezeozue): It's unclear why we need to call Acquire on the
// parent node when we're adding a child to it.
@@ -311,16 +350,55 @@
std::lock_guard<std::recursive_mutex> guard(*lock_);
if (parent_ != nullptr) {
- std::list<node*>& children = parent_->children_;
- std::list<node*>::iterator it = std::find(children.begin(), children.end(), this);
+ auto it = parent_->children_.find(this);
+ CHECK(it != parent_->children_.end());
+ parent_->children_.erase(it);
- CHECK(it != children.end());
- children.erase(it);
parent_->Release(1);
parent_ = nullptr;
}
}
+ // A custom heterogeneous comparator used for set of this node's children_ to speed up child
+ // node by name lookups.
+ //
+ // This comparator treats node* as pair (node->name_, node): two nodes* are first
+ // compared by their name using case-insenstive comparison function. If their names are equal,
+ // then pointers are compared as integers.
+ //
+ // See LookupChildByName function to see how this comparator is used.
+ //
+ // Note that it's important to first compare by name_, since it will make all nodes with same
+ // name (compared using strcasecmp) together, which allows LookupChildByName function to find
+ // range of the candidate nodes by issuing two binary searches.
+ struct NodeCompare {
+ using is_transparent = void;
+
+ bool operator()(const node* lhs, const node* rhs) const {
+ int cmp = strcasecmp(lhs->name_.c_str(), rhs->name_.c_str());
+ if (cmp != 0) {
+ return cmp < 0;
+ }
+ return reinterpret_cast<uintptr_t>(lhs) < reinterpret_cast<uintptr_t>(rhs);
+ }
+
+ bool operator()(const node* lhs, const std::pair<std::string, uintptr_t>& rhs) const {
+ int cmp = strcasecmp(lhs->name_.c_str(), rhs.first.c_str());
+ if (cmp != 0) {
+ return cmp < 0;
+ }
+ return reinterpret_cast<uintptr_t>(lhs) < rhs.second;
+ }
+
+ bool operator()(const std::pair<std::string, uintptr_t>& lhs, const node* rhs) const {
+ int cmp = strcasecmp(lhs.first.c_str(), rhs->name_.c_str());
+ if (cmp != 0) {
+ return cmp < 0;
+ }
+ return lhs.second < reinterpret_cast<uintptr_t>(rhs);
+ }
+ };
+
// A helper function to recursively construct the absolute path of a given node.
// If |safe| is true, builds a PII safe path instead
void BuildPathForNodeRecursive(bool safe, const node* node, std::stringstream* path) const;
@@ -329,9 +407,9 @@
std::string name_;
// The reference count for this node. Guarded by |lock_|.
uint32_t refcount_;
- // List of children of this node. All of them contain a back reference
+ // Set of children of this node. All of them contain a back reference
// to their parent. Guarded by |lock_|.
- std::list<node*> children_;
+ std::set<node*, NodeCompare> children_;
// Containing directory for this node. Guarded by |lock_|.
node* parent_;
// List of file handles associated with this node. Guarded by |lock_|.
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 098bb28..357cea8 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -3,6 +3,7 @@
#include "node-inl.h"
#include <algorithm>
+#include <limits>
#include <memory>
#include <mutex>
@@ -33,6 +34,9 @@
unique_node_ptr CreateNode(node* parent, const std::string& path) {
return unique_node_ptr(node::Create(parent, path, &lock_, &tracker_), &NodeTest::destroy);
}
+
+ // Expose NodeCompare for testing.
+ node::NodeCompare cmp;
};
TEST_F(NodeTest, TestCreate) {
@@ -239,3 +243,78 @@
ASSERT_EQ(mixed_child.get(), lower_child);
ASSERT_EQ(mixed_child.get(), upper_child);
}
+
+TEST_F(NodeTest, RenameSameNameSameParent) {
+ unique_node_ptr parent = CreateNode(nullptr, "/path1");
+ unique_node_ptr child = CreateNode(parent.get(), "subdir");
+
+ ASSERT_EQ(child.get(), parent->LookupChildByName("SuBdIr", false /* acquire */));
+ ASSERT_EQ(2, GetRefCount(parent.get()));
+
+ child->Rename("subdir", parent.get());
+
+ ASSERT_EQ(child.get(), parent->LookupChildByName("SuBdIr", false /* acquire */));
+ ASSERT_EQ(2, GetRefCount(parent.get()));
+}
+
+TEST_F(NodeTest, RenameRoot) {
+ unique_node_ptr root = CreateNode(nullptr, "/root");
+ ASSERT_EQ(1, GetRefCount(root.get()));
+
+ root->Rename("/i-am-root!", nullptr);
+
+ ASSERT_EQ("/i-am-root!", root->GetName());
+ ASSERT_EQ(1, GetRefCount(root.get()));
+}
+
+TEST_F(NodeTest, NodeCompareDefinesLinearOrder) {
+ unique_node_ptr node_a = CreateNode(nullptr, "a");
+ unique_node_ptr node_b = CreateNode(nullptr, "B");
+ unique_node_ptr node_c = CreateNode(nullptr, "c");
+
+ ASSERT_FALSE(cmp.operator()(node_a.get(), node_a.get()));
+ ASSERT_FALSE(cmp.operator()(node_b.get(), node_b.get()));
+ ASSERT_FALSE(cmp.operator()(node_c.get(), node_c.get()));
+
+ auto check_fn = [&](const node* lhs_node, const node* rhs_node) {
+ ASSERT_TRUE(cmp.operator()(lhs_node, rhs_node));
+ ASSERT_FALSE(cmp.operator()(rhs_node, lhs_node));
+ };
+
+ check_fn(node_a.get(), node_b.get());
+ check_fn(node_b.get(), node_c.get());
+ check_fn(node_a.get(), node_c.get());
+
+ // ("A", 0) < node_a < ("a", max_uintptr_t) < node_b
+ ASSERT_TRUE(cmp.operator()(std::make_pair("A", 0), node_a.get()));
+ ASSERT_TRUE(cmp.operator()(node_a.get(),
+ std::make_pair("A", std::numeric_limits<uintptr_t>::max())));
+ ASSERT_TRUE(cmp.operator()(std::make_pair("A", std::numeric_limits<uintptr_t>::max()),
+ node_b.get()));
+}
+
+TEST_F(NodeTest, LookupChildByName_ChildrenWithSameName) {
+ unique_node_ptr parent = CreateNode(nullptr, "/path");
+ unique_node_ptr foo1 = CreateNode(parent.get(), "FoO");
+ unique_node_ptr foo2 = CreateNode(parent.get(), "fOo");
+ unique_node_ptr bar1 = CreateNode(parent.get(), "BAR");
+ unique_node_ptr bar2 = CreateNode(parent.get(), "bar");
+ unique_node_ptr baz1 = CreateNode(parent.get(), "baZ");
+ unique_node_ptr baz2 = CreateNode(parent.get(), "Baz");
+
+ auto test_fn = [&](const std::string& name, node* first, node* second) {
+ auto node1 = parent->LookupChildByName(name, false /* acquire */);
+ ASSERT_EQ(std::min(first, second), node1);
+ node1->SetDeleted();
+
+ auto node2 = parent->LookupChildByName(name, false /* acquire */);
+ ASSERT_EQ(std::max(first, second), node2);
+ node2->SetDeleted();
+
+ ASSERT_EQ(nullptr, parent->LookupChildByName(name, false /* acquire */));
+ };
+
+ test_fn("foo", foo1.get(), foo2.get());
+ test_fn("bAr", bar1.get(), bar2.get());
+ test_fn("BaZ", baz1.get(), baz2.get());
+}
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 47f71e0..e028af4 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -2030,6 +2030,10 @@
return OsConstants.EPERM;
}
+ if (shouldBypassDatabaseForFuse(uid)) {
+ return renameInLowerFs(oldPath, newPath);
+ }
+
if (shouldBypassFuseRestrictions(/*forWrite*/ true, oldPath)
&& shouldBypassFuseRestrictions(/*forWrite*/ true, newPath)) {
return renameUncheckedForFuse(oldPath, newPath);
@@ -3292,6 +3296,9 @@
if (isCallingPackageSelf() || isCallingPackageLegacyWrite()) {
// Mutation allowed
+ } else if (isCallingPackageManager()) {
+ // Apps with MANAGE_EXTERNAL_STORAGE have all files access, hence they are
+ // allowed to insert files anywhere.
} else {
Log.w(TAG, "Ignoring mutation of " + column + " from "
+ getCallingPackageOrSelf());
@@ -6241,6 +6248,16 @@
return !isCallingIdentitySharedPackageName(appSpecificDir);
}
+ private boolean shouldBypassDatabaseForFuse(int uid) {
+ final LocalCallingIdentity token =
+ clearLocalCallingIdentity(getCachedCallingIdentityForFuse(uid));
+ try {
+ return uid != android.os.Process.SHELL_UID && isCallingPackageManager();
+ } finally {
+ restoreLocalCallingIdentity(token);
+ }
+ }
+
/**
* Set of Exif tags that should be considered for redaction.
*/
@@ -6669,6 +6686,10 @@
return OsConstants.EPERM;
}
+ if (shouldBypassDatabaseForFuse(uid)) {
+ return 0;
+ }
+
final String mimeType = MimeUtils.resolveMimeType(new File(path));
if (shouldBypassFuseRestrictions(/*forWrite*/ true, path)) {
@@ -6777,6 +6798,10 @@
return OsConstants.ENOENT;
}
+ if (shouldBypassDatabaseForFuse(uid)) {
+ return deleteFileUnchecked(path);
+ }
+
final boolean shouldBypass = shouldBypassFuseRestrictions(/*forWrite*/ true, path);
// Legacy apps that made is this far don't have the right storage permission and hence