Support transforms in file path
A critical requirement to support transforms on nodes, is retruning
different nodes for the same name depending on the requesting uid. To
do this, we have to ensure there is no dentry caching so that we get a
FUSE_LOOKUP before any IO request for nodes that support transforms
Test: Manual && fsstress for ~24hrs
Bug: 158466177
Change-Id: I73e06f6d52df7ae7ee498d537a4551a5ebb8d092
diff --git a/jni/FuseDaemon.cpp b/jni/FuseDaemon.cpp
index c332bc5..ce5f4d3 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);
@@ -415,12 +429,46 @@
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, 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:
@@ -454,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;
}
@@ -611,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;
@@ -623,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));
}
}
@@ -640,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;
@@ -719,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
@@ -999,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;
@@ -1012,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);
@@ -1165,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()) {