Merge changes I73e06f6d,I39cf223d,I93417795
* changes:
Support transforms in file path
Add JNI methods to support multiple nodes with same name
Add FUSE node fields to support multiple nodes with same name
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index 1b9163c..dfb989d 100644
--- a/jni/FuseDaemon.cpp
+++ b/jni/FuseDaemon.cpp
@@ -383,15 +383,12 @@
}
}
-static double get_timeout(struct fuse* fuse, const string& path, bool should_inval) {
- string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
- if (should_inval || path.find(media_path, 0) == 0 || is_package_owned_path(path, fuse->path)) {
+static double get_attr_timeout(const string& path, bool should_inval, node* node,
+ struct fuse* fuse) {
+ if (should_inval || is_package_owned_path(path, fuse->path)) {
// We set dentry timeout to 0 for the following reasons:
// 1. Case-insensitive lookups need to invalidate other case-insensitive dentry matches
- // 2. Installd might delete Android/media/<package> dirs when app data is cleared.
- // This can leave a stale entry in the kernel dcache, and break subsequent creation of the
- // dir via FUSE.
- // 3. With app data isolation enabled, app A should not guess existence of app B from the
+ // 2. With app data isolation enabled, app A should not guess existence of app B from the
// Android/{data,obb}/<package> paths, hence we prevent the kernel from caching that
// information.
return 0;
@@ -399,6 +396,23 @@
return std::numeric_limits<double>::max();
}
+static double get_entry_timeout(const string& path, bool should_inval, node* node,
+ struct fuse* fuse) {
+ string media_path = fuse->GetEffectiveRootPath() + "/Android/media";
+ if (path.find(media_path, 0) == 0) {
+ // Installd might delete Android/media/<package> dirs when app data is cleared.
+ // This can leave a stale entry in the kernel dcache, and break subsequent creation of the
+ // dir via FUSE.
+ return 0;
+ }
+ return get_attr_timeout(path, should_inval, node, fuse);
+}
+
+static std::string get_path(node* node) {
+ const string& io_path = node->GetIoPath();
+ return io_path.empty() ? node->BuildPath() : io_path;
+}
+
static node* make_node_entry(fuse_req_t req, node* parent, const string& name, const string& path,
struct fuse_entry_param* e, int* error_code) {
struct fuse* fuse = get_fuse(req);
@@ -412,11 +426,49 @@
}
bool should_inval = false;
+ bool transforms_complete = true;
+ int transforms = 0;
+ string io_path;
+
+ if (S_ISREG(e->attr.st_mode)) {
+ // Handle potential file transforms
+ transforms = fuse->mp->GetTransforms(path, req->ctx.uid);
+
+ if (transforms < 0) {
+ // Fail lookup if we can't fetch supported transforms for path
+ LOG(WARNING) << "Failed to fetch transforms for " << name;
+ *error_code = ENOENT;
+ return NULL;
+ }
+
+ // TODO(b/169412244): Improve JNI interaction
+ // Invalidate if there are any transforms so that we always get a lookup into userspace
+ should_inval = should_inval || transforms;
+ if (transforms) {
+ // If there are any transforms, fetch IO path
+ io_path = fuse->mp->GetIoPath(path, req->ctx.uid);
+ if (io_path.empty()) {
+ *error_code = EFAULT;
+ return NULL;
+ }
+
+ if (io_path != path) {
+ // Update size with io_path size
+ if (lstat(io_path.c_str(), &e->attr) < 0) {
+ *error_code = errno;
+ return NULL;
+ }
+ transforms_complete = false;
+ }
+ }
+ }
+
node = parent->LookupChildByName(name, true /* acquire */);
if (!node) {
- node = ::node::Create(parent, name, &fuse->lock, &fuse->tracker);
+ node = ::node::Create(parent, name, io_path, transforms_complete, transforms, &fuse->lock,
+ &fuse->tracker);
} else if (!mediaprovider::fuse::containsMount(path, std::to_string(getuid() / PER_USER_RANGE))) {
- should_inval = node->HasCaseInsensitiveMatch();
+ should_inval = should_inval || node->HasCaseInsensitiveMatch();
// Only invalidate a path if it does not contain mount.
// Invalidate both names to ensure there's no dentry left in the kernel after the following
// operations:
@@ -450,11 +502,8 @@
// reuse inode numbers.
e->generation = 0;
e->ino = fuse->ToInode(node);
- e->entry_timeout = get_timeout(fuse, path, should_inval);
- e->attr_timeout = is_package_owned_path(path, fuse->path) || should_inval
- ? 0
- : std::numeric_limits<double>::max();
-
+ e->entry_timeout = get_entry_timeout(path, should_inval, node, fuse);
+ e->attr_timeout = get_attr_timeout(path, should_inval, node, fuse);
return node;
}
@@ -607,7 +656,7 @@
fuse_reply_err(req, ENOENT);
return;
}
- string path = node->BuildPath();
+ const string& path = get_path(node);
if (!is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
fuse_reply_err(req, ENOENT);
return;
@@ -619,8 +668,10 @@
if (lstat(path.c_str(), &s) < 0) {
fuse_reply_err(req, errno);
} else {
- fuse_reply_attr(req, &s, is_package_owned_path(path, fuse->path) ?
- 0 : std::numeric_limits<double>::max());
+ fuse_reply_attr(
+ req, &s,
+ get_attr_timeout(path, node->GetTransforms() || node->HasCaseInsensitiveMatch(),
+ node, fuse));
}
}
@@ -636,7 +687,7 @@
fuse_reply_err(req, ENOENT);
return;
}
- string path = node->BuildPath();
+ const string& path = get_path(node);
if (!is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
fuse_reply_err(req, ENOENT);
return;
@@ -715,15 +766,16 @@
}
lstat(path.c_str(), attr);
- fuse_reply_attr(req, attr, is_package_owned_path(path, fuse->path) ?
- 0 : std::numeric_limits<double>::max());
+ fuse_reply_attr(req, attr,
+ get_attr_timeout(path, node->GetTransforms() || node->HasCaseInsensitiveMatch(),
+ node, fuse));
}
static void pf_canonical_path(fuse_req_t req, fuse_ino_t ino)
{
struct fuse* fuse = get_fuse(req);
node* node = fuse->FromInode(ino);
- string path = node ? node->BuildPath() : "";
+ const string& path = node ? get_path(node) : "";
if (node && is_app_accessible_path(fuse->mp, path, req->ctx.uid)) {
// TODO(b/147482155): Check that uid has access to |path| and its contents
@@ -995,7 +1047,7 @@
return;
}
const struct fuse_ctx* ctx = fuse_req_ctx(req);
- const string path = node->BuildPath();
+ const string& path = get_path(node);
if (!is_app_accessible_path(fuse->mp, path, ctx->uid)) {
fuse_reply_err(req, ENOENT);
return;
@@ -1008,6 +1060,7 @@
fi->direct_io = true;
}
+ // TODO: If transform, disallow write
int status = fuse->mp->IsOpenAllowed(path, ctx->uid, is_requesting_write(fi->flags));
if (status) {
fuse_reply_err(req, status);
@@ -1161,6 +1214,17 @@
handle* h = reinterpret_cast<handle*>(fi->fh);
struct fuse* fuse = get_fuse(req);
+ node* node = fuse->FromInode(ino);
+
+ if (!node->IsTransformsComplete()) {
+ if (!fuse->mp->Transform(node->BuildPath(), node->GetIoPath(), node->GetTransforms(),
+ req->ctx.uid)) {
+ fuse_reply_err(req, EFAULT);
+ return;
+ }
+ node->SetTransformsComplete();
+ }
+
fuse->fadviser.Record(h->fd, size);
if (h->ri->isRedactionNeeded()) {
diff --git a/jni/MediaProviderWrapper.cpp b/jni/MediaProviderWrapper.cpp
index 66ce429..0e3d3fa 100644
--- a/jni/MediaProviderWrapper.cpp
+++ b/jni/MediaProviderWrapper.cpp
@@ -281,6 +281,12 @@
/*is_static*/ false);
mid_on_file_created_ = CacheMethod(env, "onFileCreated", "(Ljava/lang/String;)V",
/*is_static*/ false);
+ mid_get_io_path_ = CacheMethod(env, "getIoPath", "(Ljava/lang/String;I)Ljava/lang/String;",
+ /*is_static*/ false);
+ mid_get_transforms_ = CacheMethod(env, "getTransforms", "(Ljava/lang/String;I)I",
+ /*is_static*/ false);
+ mid_transform_ = CacheMethod(env, "transform", "(Ljava/lang/String;Ljava/lang/String;II)Z",
+ /*is_static*/ false);
}
MediaProviderWrapper::~MediaProviderWrapper() {
@@ -424,6 +430,50 @@
return onFileCreatedInternal(env, media_provider_object_, mid_on_file_created_, path);
}
+std::string MediaProviderWrapper::GetIoPath(const std::string& path, uid_t uid) {
+ JNIEnv* env = MaybeAttachCurrentThread();
+
+ ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
+ ScopedLocalRef<jstring> j_res_path(
+ env, static_cast<jstring>(env->CallObjectMethod(media_provider_object_,
+ mid_get_io_path_, j_path.get(), uid)));
+ ScopedUtfChars j_res_utf(env, j_res_path.get());
+ if (CheckForJniException(env)) {
+ return "";
+ }
+
+ return string(j_res_utf.c_str());
+}
+
+int MediaProviderWrapper::GetTransforms(const std::string& path, uid_t uid) {
+ JNIEnv* env = MaybeAttachCurrentThread();
+
+ ScopedLocalRef<jstring> j_path(env, env->NewStringUTF(path.c_str()));
+ int res = env->CallIntMethod(media_provider_object_, mid_get_transforms_, j_path.get(), uid);
+
+ if (CheckForJniException(env)) {
+ return -1;
+ }
+
+ return res;
+}
+
+bool MediaProviderWrapper::Transform(const std::string& src, const std::string& dst, int transforms,
+ uid_t uid) {
+ JNIEnv* env = MaybeAttachCurrentThread();
+
+ ScopedLocalRef<jstring> j_src(env, env->NewStringUTF(src.c_str()));
+ ScopedLocalRef<jstring> j_dst(env, env->NewStringUTF(dst.c_str()));
+ bool res = env->CallBooleanMethod(media_provider_object_, mid_transform_, j_src.get(),
+ j_dst.get(), uid);
+
+ if (CheckForJniException(env)) {
+ return false;
+ }
+
+ return res;
+}
+
/*****************************************************************************************/
/******************************** Private member functions *******************************/
/*****************************************************************************************/
diff --git a/jni/MediaProviderWrapper.h b/jni/MediaProviderWrapper.h
index 8ada86d..9c8d778 100644
--- a/jni/MediaProviderWrapper.h
+++ b/jni/MediaProviderWrapper.h
@@ -165,6 +165,15 @@
*/
void OnFileCreated(const std::string& path);
+ /** Get path for actual I/O */
+ std::string GetIoPath(const std::string& path, uid_t uid);
+
+ /** Get supported transformations for path and transform actions for uid on path. */
+ int GetTransforms(const std::string& path, uid_t uid);
+
+ /** Transforms from src to dst file */
+ bool Transform(const std::string& src, const std::string& dst, int transforms, uid_t uid);
+
/**
* Initializes per-process static variables associated with the lifetime of
* a managed runtime.
@@ -189,6 +198,9 @@
jmethodID mid_rename_;
jmethodID mid_is_uid_for_package_;
jmethodID mid_on_file_created_;
+ jmethodID mid_get_io_path_;
+ jmethodID mid_get_transforms_;
+ jmethodID mid_transform_;
/**
* Auxiliary for caching MediaProvider methods.
diff --git a/jni/node-inl.h b/jni/node-inl.h
index 13df5a3..e5344cc 100644
--- a/jni/node-inl.h
+++ b/jni/node-inl.h
@@ -19,6 +19,7 @@
#include <android-base/logging.h>
+#include <atomic>
#include <cstdint>
#include <limits>
#include <list>
@@ -114,13 +115,14 @@
class node {
public:
// Creates a new node with the specified parent, name and lock.
- static node* Create(node* parent, const std::string& name, std::recursive_mutex* lock,
+ static node* Create(node* parent, const std::string& name, const std::string& io_path,
+ bool transforms_complete, const int transforms, std::recursive_mutex* lock,
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, lock, tracker);
+ return new node(parent, name, io_path, transforms_complete, transforms, lock, tracker);
}
// Creates a new root node. Root nodes have no parents by definition
@@ -128,7 +130,7 @@
static node* CreateRoot(const std::string& path, std::recursive_mutex* lock,
NodeTracker* tracker) {
std::lock_guard<std::recursive_mutex> guard(*lock);
- node* root = new node(nullptr, path, lock, tracker);
+ node* root = new node(nullptr, path, path, true, 0, lock, tracker);
// The root always has one extra reference to avoid it being
// accidentally collected.
@@ -176,12 +178,15 @@
// Looks up a direct descendant of this node by name. If |acquire| is true,
// also Acquire the node before returning a reference to it.
- node* LookupChildByName(const std::string& name, bool acquire) const {
- return ForChild(name, [acquire](node* child) {
- if (acquire) {
- child->Acquire();
+ 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 true;
+ return false;
});
}
@@ -253,6 +258,16 @@
return name_;
}
+ const std::string& GetIoPath() const { return io_path_; }
+
+ int GetTransforms() const { return transforms_; }
+
+ bool IsTransformsComplete() const {
+ return transforms_complete_.load(std::memory_order_acquire);
+ }
+
+ void SetTransformsComplete() { transforms_complete_.store(true, std::memory_order_release); }
+
node* GetParent() const {
std::lock_guard<std::recursive_mutex> guard(*lock_);
return parent_;
@@ -326,8 +341,12 @@
static const node* LookupAbsolutePath(const node* root, const std::string& absolute_path);
private:
- node(node* parent, const std::string& name, std::recursive_mutex* lock, NodeTracker* tracker)
+ node(node* parent, const std::string& name, const std::string& io_path, bool transforms_complete,
+ const int transforms, std::recursive_mutex* lock, NodeTracker* tracker)
: name_(name),
+ io_path_(io_path),
+ transforms_complete_(transforms_complete),
+ transforms_(transforms),
refcount_(0),
parent_(nullptr),
has_redacted_cache_(false),
@@ -454,6 +473,15 @@
// 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 determine the 'supported' and 'required' transforms to perform on node
+ // before IO. These flags should not be interpreted in native but should be passed as part
+ // of a transform function and if successful, |transforms_complete_| should be set to true
+ const int transforms_;
// 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
diff --git a/jni/node_test.cpp b/jni/node_test.cpp
index 90f0894..d2ba399 100644
--- a/jni/node_test.cpp
+++ b/jni/node_test.cpp
@@ -31,8 +31,9 @@
typedef std::unique_ptr<node, decltype(&NodeTest::destroy)> unique_node_ptr;
- unique_node_ptr CreateNode(node* parent, const std::string& path) {
- return unique_node_ptr(node::Create(parent, path, &lock_, &tracker_), &NodeTest::destroy);
+ unique_node_ptr CreateNode(node* parent, const std::string& path, const int transforms = 0) {
+ return unique_node_ptr(node::Create(parent, path, "", true, transforms, &lock_, &tracker_),
+ &NodeTest::destroy);
}
static class node* ForChild(class node* node, const std::string& name,
@@ -66,7 +67,7 @@
}
TEST_F(NodeTest, TestRelease) {
- node* node = node::Create(nullptr, "/path", &lock_, &tracker_);
+ node* node = node::Create(nullptr, "/path", "", true, 0, &lock_, &tracker_);
acquire(node);
acquire(node);
ASSERT_EQ(3, GetRefCount(node));
@@ -141,57 +142,96 @@
TEST_F(NodeTest, TestRenameNameForChild) {
unique_node_ptr parent = CreateNode(nullptr, "/path");
- unique_node_ptr child = CreateNode(parent.get(), "subdir");
- ASSERT_EQ(2, GetRefCount(parent.get()));
- ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
+ unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+ unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
+ ASSERT_EQ(3, GetRefCount(parent.get()));
+ ASSERT_EQ(child0.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
parent->RenameChild("subdir", "subdir_new", parent.get());
- ASSERT_EQ(2, GetRefCount(parent.get()));
- ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
- ASSERT_EQ(child.get(), parent->LookupChildByName("subdir_new", false /* acquire */));
+ ASSERT_EQ(3, GetRefCount(parent.get()));
+ ASSERT_EQ(nullptr,
+ parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+ ASSERT_EQ(child0.get(),
+ parent->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
- ASSERT_EQ("/path/subdir_new", child->BuildPath());
- ASSERT_EQ(1, GetRefCount(child.get()));
+ ASSERT_EQ("/path/subdir_new", child0->BuildPath());
+ ASSERT_EQ("/path/subdir_new", child1->BuildPath());
+ ASSERT_EQ(1, GetRefCount(child0.get()));
+ ASSERT_EQ(1, GetRefCount(child1.get()));
}
TEST_F(NodeTest, TestRenameParentForChild) {
unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
- unique_node_ptr child = CreateNode(parent1.get(), "subdir");
- ASSERT_EQ(2, GetRefCount(parent1.get()));
- ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
+ unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
+ unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
+ ASSERT_EQ(3, GetRefCount(parent1.get()));
+ ASSERT_EQ(child0.get(),
+ parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
parent1->RenameChild("subdir", "subdir", parent2.get());
ASSERT_EQ(1, GetRefCount(parent1.get()));
- ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
- ASSERT_EQ(2, GetRefCount(parent2.get()));
- ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir", false /* acquire */));
+ ASSERT_EQ(3, GetRefCount(parent2.get()));
+ ASSERT_EQ(child0.get(),
+ parent2->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent2->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
- ASSERT_EQ("/path2/subdir", child->BuildPath());
- ASSERT_EQ(1, GetRefCount(child.get()));
+ ASSERT_EQ("/path2/subdir", child0->BuildPath());
+ ASSERT_EQ("/path2/subdir", child1->BuildPath());
+ ASSERT_EQ(1, GetRefCount(child0.get()));
+ ASSERT_EQ(1, GetRefCount(child1.get()));
}
TEST_F(NodeTest, TestRenameNameAndParentForChild) {
unique_node_ptr parent1 = CreateNode(nullptr, "/path1");
unique_node_ptr parent2 = CreateNode(nullptr, "/path2");
- unique_node_ptr child = CreateNode(parent1.get(), "subdir");
- ASSERT_EQ(2, GetRefCount(parent1.get()));
- ASSERT_EQ(child.get(), parent1->LookupChildByName("subdir", false /* acquire */));
+ unique_node_ptr child0 = CreateNode(parent1.get(), "subdir", 0 /* transforms */);
+ unique_node_ptr child1 = CreateNode(parent1.get(), "subdir", 1 /* transforms */);
+ ASSERT_EQ(3, GetRefCount(parent1.get()));
+ ASSERT_EQ(child0.get(),
+ parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
parent1->RenameChild("subdir", "subdir_new", parent2.get());
ASSERT_EQ(1, GetRefCount(parent1.get()));
- ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir", false /* acquire */));
- ASSERT_EQ(nullptr, parent1->LookupChildByName("subdir_new", false /* acquire */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
- ASSERT_EQ(2, GetRefCount(parent2.get()));
- ASSERT_EQ(child.get(), parent2->LookupChildByName("subdir_new", false /* acquire */));
+ ASSERT_EQ(3, GetRefCount(parent2.get()));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir_new", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent1->LookupChildByName("subdir_new", false /* acquire */, 1 /* transforms */));
- ASSERT_EQ("/path2/subdir_new", child->BuildPath());
- ASSERT_EQ(1, GetRefCount(child.get()));
+ ASSERT_EQ("/path2/subdir_new", child0->BuildPath());
+ ASSERT_EQ("/path2/subdir_new", child1->BuildPath());
+ ASSERT_EQ(1, GetRefCount(child0.get()));
+ ASSERT_EQ(1, GetRefCount(child1.get()));
}
TEST_F(NodeTest, TestBuildPath) {
@@ -219,21 +259,28 @@
TEST_F(NodeTest, TestSetDeletedForChild) {
unique_node_ptr parent = CreateNode(nullptr, "/path");
- unique_node_ptr child = CreateNode(parent.get(), "subdir");
+ unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+ unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
- ASSERT_EQ(child.get(), parent->LookupChildByName("subdir", false /* acquire */));
+ ASSERT_EQ(child0.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
parent->SetDeletedForChild("subdir");
- ASSERT_EQ(nullptr, parent->LookupChildByName("subdir", false /* acquire */));
+ ASSERT_EQ(nullptr,
+ parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
}
TEST_F(NodeTest, DeleteTree) {
unique_node_ptr parent = CreateNode(nullptr, "/path");
// This is the tree that we intend to delete.
- node* child = node::Create(parent.get(), "subdir", &lock_, &tracker_);
- node::Create(child, "s1", &lock_, &tracker_);
- node* subchild2 = node::Create(child, "s2", &lock_, &tracker_);
- node::Create(subchild2, "sc2", &lock_, &tracker_);
+ node* child = node::Create(parent.get(), "subdir", "", true, 0, &lock_, &tracker_);
+ node::Create(child, "s1", "", true, 0, &lock_, &tracker_);
+ node* subchild2 = node::Create(child, "s2", "", true, 0, &lock_, &tracker_);
+ node::Create(subchild2, "sc2", "", true, 0, &lock_, &tracker_);
ASSERT_EQ(child, parent->LookupChildByName("subdir", false /* acquire */));
node::DeleteTree(child);
@@ -248,6 +295,20 @@
ASSERT_EQ(nullptr, parent->LookupChildByName("", false /* acquire */));
}
+TEST_F(NodeTest, LookupChildByName_transforms) {
+ unique_node_ptr parent = CreateNode(nullptr, "/path");
+ unique_node_ptr child0 = CreateNode(parent.get(), "subdir", 0 /* transforms */);
+ unique_node_ptr child1 = CreateNode(parent.get(), "subdir", 1 /* transforms */);
+
+ ASSERT_EQ(child0.get(), parent->LookupChildByName("subdir", false /* acquire */));
+ ASSERT_EQ(child0.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 0 /* transforms */));
+ ASSERT_EQ(child1.get(),
+ parent->LookupChildByName("subdir", false /* acquire */, 1 /* transforms */));
+ ASSERT_EQ(nullptr,
+ parent->LookupChildByName("subdir", false /* acquire */, 2 /* transforms */));
+}
+
TEST_F(NodeTest, LookupChildByName_refcounts) {
unique_node_ptr parent = CreateNode(nullptr, "/path");
unique_node_ptr child = CreateNode(parent.get(), "subdir");
diff --git a/src/com/android/providers/media/MediaProvider.java b/src/com/android/providers/media/MediaProvider.java
index 3ad143f..717786d 100644
--- a/src/com/android/providers/media/MediaProvider.java
+++ b/src/com/android/providers/media/MediaProvider.java
@@ -1144,6 +1144,65 @@
}
/**
+ * Called from FUSE to transform a file
+ *
+ * A transform can change the file contents for {@code uid} from {@code src} to {@code dst}
+ * depending on {@code flags}. This allows the FUSE daemon serve different file contents for
+ * the same file to different apps.
+ *
+ * The only supported transform for now is transcoding which re-encodes a file taken in a modern
+ * format like HEVC to a legacy format like AVC.
+ *
+ * @param src file path to transform
+ * @param dst file path to save transformed file
+ * @param flags determines the kind of transform
+ * @param uid app requesting transform
+ *
+ * Called from JNI in jni/MediaProviderWrapper.cpp
+ */
+ @Keep
+ public boolean transformForFuse(String src, String dst, int flags, int uid) {
+ // TODO: Add logic
+ return true;
+ }
+
+ /**
+ * Called from FUSE to get IO path for {@code uid}
+ *
+ * IO path is the actual path to be used on the lower fs for IO via FUSE. For some file
+ * transforms, this path might be different from the path the app is requesting IO on.
+ *
+ * @param path file path to get an IO path for
+ * @param uid app requesting IO
+ *
+ * Called from JNI in jni/MediaProviderWrapper.cpp
+ */
+ @Keep
+ public String getIoPathForFuse(String path, int uid) {
+ // TODO: Add logic
+ return "";
+ }
+
+ /**
+ * Called from FUSE to get transforms for {@code uid}
+ *
+ * If transforms are not supported for {@code path}, {@code 0} will be returned. Otherwise,
+ * a bitwise OR of supported transforms for {@code path} and actual transforms to perform for
+ * {@code uid} will be returned.
+ *
+ * @param path file path to get transforms for
+ * @param uid app requesting IO
+ *
+ * Called from JNI in jni/MediaProviderWrapper.cpp
+ * @see {@link transformForFuse}
+ */
+ @Keep
+ public int getTransformsForFuse(String path, int uid) {
+ // TODO: Add logic
+ return 0;
+ }
+
+ /**
* Returns true if the app denoted by the given {@code uid} and {@code packageName} is allowed
* to clear other apps' cache directories.
*/