You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
557 lines
20 KiB
557 lines
20 KiB
/*
|
|
* 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 <android-base/logging.h>
|
|
|
|
#include <sys/types.h>
|
|
#include <atomic>
|
|
#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"
|
|
#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<const RedactionInfo> 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<std::shared_ptr<DirectoryEntry>> 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<const class node*>(ino);
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
CHECK(active_nodes_.find(node) != active_nodes_.end());
|
|
}
|
|
}
|
|
|
|
void NodeDeleted(const node* node) {
|
|
if (kEnableInodeTracking) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
LOG(DEBUG) << "Node: " << reinterpret_cast<uintptr_t>(node) << " deleted.";
|
|
|
|
CHECK(active_nodes_.find(node) != active_nodes_.end());
|
|
active_nodes_.erase(node);
|
|
}
|
|
}
|
|
|
|
void NodeCreated(const node* node) {
|
|
if (kEnableInodeTracking) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
LOG(DEBUG) << "Node: " << reinterpret_cast<uintptr_t>(node) << " created.";
|
|
|
|
CHECK(active_nodes_.find(node) == active_nodes_.end());
|
|
active_nodes_.insert(node);
|
|
}
|
|
}
|
|
|
|
private:
|
|
std::recursive_mutex* lock_;
|
|
std::unordered_set<const node*> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<node*>(static_cast<uintptr_t>(ino));
|
|
}
|
|
|
|
// Maps a node to its associated inode.
|
|
static __u64 ToInode(node* node) {
|
|
return static_cast<__u64>(reinterpret_cast<uintptr_t>(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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<std::recursive_mutex> 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<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));
|
|
}
|
|
|
|
void DestroyHandle(handle* h) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
|
|
auto comp = [h](const std::unique_ptr<handle>& 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<std::recursive_mutex> guard(*lock_);
|
|
|
|
for (const auto& handle : handles_) {
|
|
if (handle->cached) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool ShouldInvalidate() const {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
return should_invalidate_;
|
|
}
|
|
|
|
void SetShouldInvalidate() {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
should_invalidate_ = true;
|
|
}
|
|
|
|
bool HasRedactedCache() const {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
return has_redacted_cache_;
|
|
}
|
|
|
|
void SetRedactedCache(bool state) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
has_redacted_cache_ = state;
|
|
}
|
|
|
|
inline void AddDirHandle(dirhandle* d) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
|
|
dirhandles_.emplace_back(std::unique_ptr<dirhandle>(d));
|
|
}
|
|
|
|
void DestroyDirHandle(dirhandle* d) {
|
|
std::lock_guard<std::recursive_mutex> guard(*lock_);
|
|
|
|
auto comp = [d](const std::unique_ptr<dirhandle>& 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<std::recursive_mutex> guard(*lock_);
|
|
refcount_++;
|
|
}
|
|
|
|
// Adds this node to a specified parent.
|
|
void AddToParent(node* parent) {
|
|
std::lock_guard<std::recursive_mutex> 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<std::recursive_mutex> 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<bool(node*)>& callback) const {
|
|
std::lock_guard<std::recursive_mutex> 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<uintptr_t>::max()));
|
|
|
|
// Make a copy of the matches because calling callback might modify the list which will
|
|
// cause issues while iterating over them.
|
|
std::vector<node*> 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<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;
|
|
|
|
// 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<node*, NodeCompare> children_;
|
|
// Containing directory for this node. Guarded by |lock_|.
|
|
node* parent_;
|
|
// List of file handles associated with this node. Guarded by |lock_|.
|
|
std::vector<std::unique_ptr<handle>> handles_;
|
|
// List of directory handles associated with this node. Guarded by |lock_|.
|
|
std::vector<std::unique_ptr<dirhandle>> 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_
|