/* * Copyright (C) 2020 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specic language governing permissions and * limitations under the License. */ #ifndef MEDIA_PROVIDER_JNI_NODE_INL_H_ #define MEDIA_PROVIDER_JNI_NODE_INL_H_ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libfuse_jni/ReaddirHelper.h" #include "libfuse_jni/RedactionInfo.h" class NodeTest; namespace mediaprovider { namespace fuse { struct handle { explicit handle(int fd, const RedactionInfo* ri, bool cached, bool passthrough, uid_t uid, uid_t transforms_uid) : fd(fd), ri(ri), cached(cached), passthrough(passthrough), uid(uid), transforms_uid(transforms_uid) { CHECK(ri != nullptr); } const int fd; const std::unique_ptr ri; const bool cached; const bool passthrough; const uid_t uid; const uid_t transforms_uid; ~handle() { close(fd); } }; struct dirhandle { explicit dirhandle(DIR* dir) : d(dir), next_off(0) { CHECK(dir != nullptr); } DIR* const d; off_t next_off; // Fuse readdir() is called multiple times based on the size of the buffer and // number of directory entries in the given directory. 'de' holds the list // of directory entries for the directory handle and this list is available // across subsequent readdir() calls for the same directory handle. std::vector> de; ~dirhandle() { closedir(d); } }; // Whether inode tracking is enabled or not. When enabled, we maintain a // separate mapping from inode numbers to "live" nodes so we can detect when // we receive a request to a node that has been deleted. static constexpr bool kEnableInodeTracking = true; class node; // Tracks the set of active nodes associated with a FUSE instance so that we // can assert that we only ever return an active node in response to a lookup. class NodeTracker { public: explicit NodeTracker(std::recursive_mutex* lock) : lock_(lock) {} void CheckTracked(__u64 ino) const { if (kEnableInodeTracking) { const node* node = reinterpret_cast(ino); std::lock_guard guard(*lock_); CHECK(active_nodes_.find(node) != active_nodes_.end()); } } void NodeDeleted(const node* node) { if (kEnableInodeTracking) { std::lock_guard guard(*lock_); LOG(DEBUG) << "Node: " << reinterpret_cast(node) << " deleted."; CHECK(active_nodes_.find(node) != active_nodes_.end()); active_nodes_.erase(node); } } void NodeCreated(const node* node) { if (kEnableInodeTracking) { std::lock_guard guard(*lock_); LOG(DEBUG) << "Node: " << reinterpret_cast(node) << " created."; CHECK(active_nodes_.find(node) == active_nodes_.end()); active_nodes_.insert(node); } } private: std::recursive_mutex* lock_; std::unordered_set active_nodes_; }; class node { public: // Creates a new node with the specified parent, name and lock. static node* Create(node* parent, const std::string& name, const std::string& io_path, bool should_invalidate, bool transforms_complete, const int transforms, const int transforms_reason, std::recursive_mutex* lock, ino_t ino, NodeTracker* tracker) { // Place the entire constructor under a critical section to make sure // node creation, tracking (if enabled) and the addition to a parent are // atomic. std::lock_guard guard(*lock); return new node(parent, name, io_path, should_invalidate, transforms_complete, transforms, transforms_reason, lock, ino, tracker); } // Creates a new root node. Root nodes have no parents by definition // and their "name" must signify an absolute path. static node* CreateRoot(const std::string& path, std::recursive_mutex* lock, ino_t ino, NodeTracker* tracker) { std::lock_guard guard(*lock); node* root = new node(nullptr, path, path, false /* should_invalidate */, true /* transforms_complete */, 0 /* transforms */, 0 /* transforms_reason */, lock, ino, tracker); // The root always has one extra reference to avoid it being // accidentally collected. root->Acquire(); return root; } // Maps an inode to its associated node. static inline node* FromInode(__u64 ino, const NodeTracker* tracker) { tracker->CheckTracked(ino); return reinterpret_cast(static_cast(ino)); } // Maps a node to its associated inode. static __u64 ToInode(node* node) { return static_cast<__u64>(reinterpret_cast(node)); } // Releases a reference to a node. Returns true iff the refcount dropped to // zero as a result of this call to Release, meaning that it's no longer // safe to perform any operations on references to this node. bool Release(uint32_t count) { std::lock_guard guard(*lock_); if (refcount_ >= count) { refcount_ -= count; if (refcount_ == 0) { delete this; return true; } } else { LOG(ERROR) << "Mismatched reference count: refcount_ = " << this->refcount_ << " ,count = " << count; } return false; } // Builds the full path associated with this node, including all path segments // associated with its descendants. std::string BuildPath() const; // Builds the full PII safe path associated with this node, including all path segments // associated with its descendants. std::string BuildSafePath() const; // Looks up a direct descendant of this node by case-insensitive |name|. If |acquire| is true, // also Acquire the node before returning a reference to it. // |transforms| is an opaque flag that is used to distinguish multiple nodes sharing the same // |name| but requiring different IO transformations as determined by the MediaProvider. node* LookupChildByName(const std::string& name, bool acquire, const int transforms = 0) const { return ForChild(name, [acquire, transforms](node* child) { if (child->transforms_ == transforms) { if (acquire) { child->Acquire(); } return true; } return false; }); } // Marks this node children as deleted. They are still associated with their parent, and // all open handles etc. to the deleted nodes are preserved until their refcount goes // to zero. void SetDeletedForChild(const std::string& name) { ForChild(name, [](node* child) { child->SetDeleted(); return false; }); } void SetDeleted() { std::lock_guard guard(*lock_); deleted_ = true; } void RenameChild(const std::string& old_name, const std::string& new_name, node* new_parent) { ForChild(old_name, [=](node* child) { child->Rename(new_name, new_parent); return false; }); } void Rename(const std::string& name, node* new_parent) { std::lock_guard guard(*lock_); 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); } } const std::string& GetName() const { std::lock_guard guard(*lock_); return name_; } const std::string& GetIoPath() const { return io_path_; } int GetTransforms() const { return transforms_; } int GetTransformsReason() const { return transforms_reason_; } bool IsTransformsComplete() const { return transforms_complete_.load(std::memory_order_acquire); } void SetTransformsComplete(bool complete) { transforms_complete_.store(complete, std::memory_order_release); } node* GetParent() const { std::lock_guard guard(*lock_); return parent_; } inline void AddHandle(handle* h) { std::lock_guard guard(*lock_); handles_.emplace_back(std::unique_ptr(h)); } void DestroyHandle(handle* h) { std::lock_guard guard(*lock_); auto comp = [h](const std::unique_ptr& ptr) { return ptr.get() == h; }; auto it = std::find_if(handles_.begin(), handles_.end(), comp); CHECK(it != handles_.end()); handles_.erase(it); } bool HasCachedHandle() const { std::lock_guard guard(*lock_); for (const auto& handle : handles_) { if (handle->cached) { return true; } } return false; } bool ShouldInvalidate() const { std::lock_guard guard(*lock_); return should_invalidate_; } void SetShouldInvalidate() { std::lock_guard guard(*lock_); should_invalidate_ = true; } bool HasRedactedCache() const { std::lock_guard guard(*lock_); return has_redacted_cache_; } void SetRedactedCache(bool state) { std::lock_guard guard(*lock_); has_redacted_cache_ = state; } inline void AddDirHandle(dirhandle* d) { std::lock_guard guard(*lock_); dirhandles_.emplace_back(std::unique_ptr(d)); } void DestroyDirHandle(dirhandle* d) { std::lock_guard guard(*lock_); auto comp = [d](const std::unique_ptr& ptr) { return ptr.get() == d; }; auto it = std::find_if(dirhandles_.begin(), dirhandles_.end(), comp); CHECK(it != dirhandles_.end()); dirhandles_.erase(it); } // Deletes the tree of nodes rooted at |tree|. static void DeleteTree(node* tree); // Looks up an absolute path rooted at |root|, or nullptr if no such path // through the hierarchy exists. static const node* LookupAbsolutePath(const node* root, const std::string& absolute_path); // Looks up for the node with the given ino rooted at |root|, or nullptr if no such node exists. static const node* LookupInode(const node* root, ino_t ino); private: node(node* parent, const std::string& name, const std::string& io_path, const bool should_invalidate, const bool transforms_complete, const int transforms, const int transforms_reason, std::recursive_mutex* lock, ino_t ino, NodeTracker* tracker) : name_(name), io_path_(io_path), transforms_complete_(transforms_complete), transforms_(transforms), transforms_reason_(transforms_reason), refcount_(0), parent_(nullptr), has_redacted_cache_(false), should_invalidate_(should_invalidate), deleted_(false), lock_(lock), ino_(ino), tracker_(tracker) { tracker_->NodeCreated(this); Acquire(); // This is a special case for the root node. All other nodes will have a // non-null parent. if (parent != nullptr) { AddToParent(parent); } // If the node requires transforms, we MUST never cache it in the VFS if (transforms) { CHECK(should_invalidate_); } } // Acquires a reference to a node. This maps to the "lookup count" specified // by the FUSE documentation and must only happen under the circumstances // documented in libfuse/include/fuse_lowlevel.h. inline void Acquire() { std::lock_guard guard(*lock_); refcount_++; } // Adds this node to a specified parent. void AddToParent(node* parent) { std::lock_guard guard(*lock_); // This method assumes this node is currently unparented. CHECK(parent_ == nullptr); // Check that the new parent isn't nullptr either. CHECK(parent != nullptr); parent_ = parent; 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. parent_->Acquire(); } // Removes this node from its current parent, and set its parent to nullptr. void RemoveFromParent() { std::lock_guard guard(*lock_); if (parent_ != nullptr) { auto it = parent_->children_.find(this); CHECK(it != parent_->children_.end()); parent_->children_.erase(it); parent_->Release(1); parent_ = nullptr; } } // Finds *all* non-deleted nodes matching |name| and runs the function |callback| on each // node until |callback| returns true. // When |callback| returns true, the matched node is returned node* ForChild(const std::string& name, const std::function& callback) const { std::lock_guard guard(*lock_); // 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::max())); // Make a copy of the matches because calling callback might modify the list which will // cause issues while iterating over them. std::vector children(start, end); for (node* child : children) { if (!child->deleted_ && callback(child)) { return child; } } return 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(lhs) < reinterpret_cast(rhs); } bool operator()(const node* lhs, const std::pair& rhs) const { int cmp = strcasecmp(lhs->name_.c_str(), rhs.first.c_str()); if (cmp != 0) { return cmp < 0; } return reinterpret_cast(lhs) < rhs.second; } bool operator()(const std::pair& 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(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; // The name of this node. Non-const because it can change during renames. std::string name_; // Filesystem path that will be used for IO (if it is non-empty) instead of node->BuildPath const std::string io_path_; // Whether any transforms required on |io_path_| are complete. // If false, might need to call a node transform function with |transforms| below std::atomic_bool transforms_complete_; // Opaque flags that determines the 'required' transforms to perform on node // before IO. These flags should not be interpreted in native but should be passed to the // MediaProvider as part of a transform function and if successful, |transforms_complete_| // should be set to true const int transforms_; // Opaque value indicating the reason why transforms are required. // This value should not be interpreted in native but should be passed to the MediaProvider // as part of a transform function const int transforms_reason_; // The reference count for this node. Guarded by |lock_|. uint32_t refcount_; // Set of children of this node. All of them contain a back reference // to their parent. Guarded by |lock_|. std::set children_; // Containing directory for this node. Guarded by |lock_|. node* parent_; // List of file handles associated with this node. Guarded by |lock_|. std::vector> handles_; // List of directory handles associated with this node. Guarded by |lock_|. std::vector> dirhandles_; bool has_redacted_cache_; bool should_invalidate_; bool deleted_; std::recursive_mutex* lock_; // Inode number of the file represented by this node. const ino_t ino_; NodeTracker* const tracker_; ~node() { RemoveFromParent(); handles_.clear(); dirhandles_.clear(); tracker_->NodeDeleted(this); } friend class ::NodeTest; }; } // namespace fuse } // namespace mediaprovider #endif // MEDIA_PROVIDER_JNI_MODE_INL_H_