blob: 4e8de888dc654b124c07407853bab979709ad8e8 [file] [log] [blame]
/*
* Copyright (c) 1998-2014 Erez Zadok
* Copyright (c) 2009 Shrikar Archak
* Copyright (c) 2003-2014 Stony Brook University
* Copyright (c) 2003-2014 The Research Foundation of SUNY
* Copyright (C) 2013-2014 Motorola Mobility, LLC
* Copyright (C) 2017 Google, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include "esdfs.h"
struct esdfs_name_data {
struct dir_context ctx;
const struct qstr *to_find;
char match_name[NAME_MAX+1];
bool found;
};
static int esdfs_name_match(struct dir_context *ctx, const char *name, int namelen,
loff_t offset, u64 ino, unsigned int d_type)
{
struct esdfs_name_data *buf = container_of(ctx, struct esdfs_name_data, ctx);
struct qstr candidate = QSTR_INIT(name, namelen);
if (qstr_case_eq(buf->to_find, &candidate)) {
memcpy(buf->match_name, name, namelen);
buf->match_name[namelen] = 0;
buf->found = true;
return 1;
}
return 0;
}
int esdfs_lookup_nocase(struct path *parent,
const struct qstr *name,
struct path *path) {
int err = 0;
/* Use vfs_path_lookup to check if the dentry exists or not */
err = vfs_path_lookup(parent->dentry, parent->mnt, name->name, 0, path);
/* check for other cases */
if (err == -ENOENT) {
struct file *file;
const struct cred *cred = current_cred();
struct esdfs_name_data buffer = {
.ctx.actor = esdfs_name_match,
.to_find = name,
.found = false,
};
file = dentry_open(parent, O_RDONLY | O_DIRECTORY, cred);
if (IS_ERR(file))
return PTR_ERR(file);
err = iterate_dir(file, &buffer.ctx);
fput(file);
if (err)
return err;
if (buffer.found)
err = vfs_path_lookup(parent->dentry, parent->mnt,
buffer.match_name, 0, path);
else
err = -ENOENT;
}
return err;
}
struct esdfs_ci_getdents_callback {
struct dir_context ctx;
const char *name;
char match_name[NAME_MAX+1];
int found; /*-1: not found, 0: found*/
int count;
};
/* The dentry cache is just so we have properly sized dentries */
static struct kmem_cache *esdfs_dentry_cachep;
int esdfs_init_dentry_cache(void)
{
esdfs_dentry_cachep =
kmem_cache_create("esdfs_dentry",
sizeof(struct esdfs_dentry_info),
0, SLAB_RECLAIM_ACCOUNT, NULL);
return esdfs_dentry_cachep ? 0 : -ENOMEM;
}
void esdfs_destroy_dentry_cache(void)
{
if (esdfs_dentry_cachep)
kmem_cache_destroy(esdfs_dentry_cachep);
}
void esdfs_free_dentry_private_data(struct dentry *dentry)
{
kmem_cache_free(esdfs_dentry_cachep, dentry->d_fsdata);
dentry->d_fsdata = NULL;
}
/* allocate new dentry private data */
int esdfs_new_dentry_private_data(struct dentry *dentry)
{
struct esdfs_dentry_info *info = ESDFS_D(dentry);
/* use zalloc to init dentry_info.lower_path */
info = kmem_cache_zalloc(esdfs_dentry_cachep, GFP_ATOMIC);
if (!info)
return -ENOMEM;
spin_lock_init(&info->lock);
dentry->d_fsdata = info;
return 0;
}
struct inode_data {
struct inode *lower_inode;
uint32_t id;
};
/* Multiple obb files can point to the same lower file */
static int esdfs_inode_test(struct inode *inode, void *candidate_data)
{
struct inode *current_lower_inode = esdfs_lower_inode(inode);
uint32_t current_userid = ESDFS_I(inode)->userid;
struct inode_data *data = (struct inode_data *)candidate_data;
if (current_lower_inode == data->lower_inode
&& current_userid == data->id)
return 1; /* found a match */
else
return 0; /* no match */
}
static int esdfs_inode_set(struct inode *inode, void *lower_inode)
{
/* we do actual inode initialization in esdfs_iget */
return 0;
}
struct inode *esdfs_iget(struct super_block *sb, struct inode *lower_inode,
uint32_t id)
{
struct esdfs_inode_info *info;
struct inode_data data;
struct inode *inode; /* the new inode to return */
if (!igrab(lower_inode))
return ERR_PTR(-ESTALE);
data.id = id;
data.lower_inode = lower_inode;
inode = iget5_locked(sb, /* our superblock */
/*
* hashval: we use inode number, but we can
* also use "(unsigned long)lower_inode"
* instead.
*/
lower_inode->i_ino, /* hashval */
esdfs_inode_test, /* inode comparison function */
esdfs_inode_set, /* inode init function */
&data); /* data passed to test+set fxns */
if (!inode) {
iput(lower_inode);
return ERR_PTR(-ENOMEM);
}
/* if found a cached inode, then just return it (after iput) */
if (!(inode->i_state & I_NEW)) {
iput(lower_inode);
return inode;
}
/* initialize new inode */
info = ESDFS_I(inode);
info->tree = ESDFS_TREE_NONE;
info->userid = 0;
info->appid = 0;
info->under_obb = false;
inode->i_ino = lower_inode->i_ino;
esdfs_set_lower_inode(inode, lower_inode);
inode->i_version++;
/* use different set of inode ops for symlinks & directories */
if (S_ISDIR(lower_inode->i_mode))
inode->i_op = &esdfs_dir_iops;
else if (S_ISLNK(lower_inode->i_mode))
inode->i_op = &esdfs_symlink_iops;
else
inode->i_op = &esdfs_main_iops;
/* use different set of file ops for directories */
if (S_ISDIR(lower_inode->i_mode))
inode->i_fop = &esdfs_dir_fops;
else
inode->i_fop = &esdfs_main_fops;
inode->i_mapping->a_ops = &esdfs_aops;
inode->i_atime.tv_sec = 0;
inode->i_atime.tv_nsec = 0;
inode->i_mtime.tv_sec = 0;
inode->i_mtime.tv_nsec = 0;
inode->i_ctime.tv_sec = 0;
inode->i_ctime.tv_nsec = 0;
/* properly initialize special inodes */
if (S_ISBLK(lower_inode->i_mode) || S_ISCHR(lower_inode->i_mode) ||
S_ISFIFO(lower_inode->i_mode) || S_ISSOCK(lower_inode->i_mode))
init_special_inode(inode, lower_inode->i_mode,
lower_inode->i_rdev);
/* all well, copy inode attributes */
esdfs_copy_lower_attr(inode, lower_inode);
fsstack_copy_inode_size(inode, lower_inode);
unlock_new_inode(inode);
return inode;
}
/*
* Helper interpose routine, called directly by ->lookup to handle
* spliced dentries
*/
static struct dentry *__esdfs_interpose(struct dentry *dentry,
struct super_block *sb,
struct path *lower_path,
uint32_t id)
{
struct inode *inode;
struct inode *lower_inode;
struct super_block *lower_sb;
struct dentry *ret_dentry;
lower_inode = lower_path->dentry->d_inode;
lower_sb = esdfs_lower_super(sb);
/* check that the lower file system didn't cross a mount point */
if (lower_inode->i_sb != lower_sb) {
ret_dentry = ERR_PTR(-EXDEV);
goto out;
}
/*
* We allocate our new inode below by calling esdfs_iget,
* which will initialize some of the new inode's fields
*/
/* inherit lower inode number for esdfs's inode */
inode = esdfs_iget(sb, lower_inode, id);
if (IS_ERR(inode)) {
ret_dentry = ERR_CAST(inode);
goto out;
}
ret_dentry = d_splice_alias(inode, dentry);
dentry = ret_dentry ?: dentry;
if (IS_ERR(dentry))
goto out;
if (ESDFS_DERIVE_PERMS(ESDFS_SB(sb)))
esdfs_derive_perms(dentry);
esdfs_set_perms(inode);
out:
return ret_dentry;
}
/*
* Connect an esdfs inode dentry/inode with several lower ones. This is
* the classic stackable file system "vnode interposition" action.
*
* @dentry: esdfs's dentry which interposes on lower one
* @sb: esdfs's super_block
* @lower_path: the lower path (caller does path_get/put)
*/
int esdfs_interpose(struct dentry *dentry, struct super_block *sb,
struct path *lower_path, uint32_t id)
{
struct dentry *ret_dentry;
ret_dentry = __esdfs_interpose(dentry, sb, lower_path, id);
return PTR_ERR(ret_dentry);
}
/*
* Main driver function for esdfs's lookup.
*
* Returns: NULL (ok), ERR_PTR if an error occurred.
* Fills in lower_parent_path with <dentry,mnt> on success.
*/
static struct dentry *__esdfs_lookup(struct dentry *dentry,
unsigned int flags,
struct path *lower_parent_path,
uint32_t id, bool use_dl)
{
int err = 0;
struct vfsmount *lower_dir_mnt;
struct dentry *lower_dir_dentry = NULL;
struct dentry *lower_dentry;
const char *name;
struct path lower_path;
struct qstr dname;
struct dentry *ret_dentry = NULL;
/* must initialize dentry operations */
d_set_d_op(dentry, &esdfs_dops);
if (IS_ROOT(dentry))
goto out;
if (use_dl)
name = ESDFS_SB(dentry->d_sb)->dl_name.name;
else
name = dentry->d_name.name;
dname.name = name;
dname.len = strlen(name);
/* now start the actual lookup procedure */
lower_dir_dentry = lower_parent_path->dentry;
lower_dir_mnt = lower_parent_path->mnt;
/* if the access is to the Download directory, redirect
* to lower path.
*/
if (use_dl) {
pathcpy(&lower_path, &ESDFS_SB(dentry->d_sb)->dl_path);
path_get(&ESDFS_SB(dentry->d_sb)->dl_path);
} else {
err = esdfs_lookup_nocase(lower_parent_path, &dname,
&lower_path);
}
/* no error: handle positive dentries */
if (!err) {
esdfs_set_lower_path(dentry, &lower_path);
ret_dentry =
__esdfs_interpose(dentry, dentry->d_sb,
&lower_path, id);
if (IS_ERR(ret_dentry)) {
err = PTR_ERR(ret_dentry);
/* path_put underlying underlying path on error */
esdfs_put_reset_lower_paths(dentry);
}
goto out;
}
/*
* We don't consider ENOENT an error, and we want to return a
* negative dentry.
*/
if (err && err != -ENOENT)
goto out;
/* instatiate a new negative dentry */
/* See if the low-level filesystem might want
* to use its own hash */
lower_dentry = d_hash_and_lookup(lower_dir_dentry, &dname);
if (IS_ERR(lower_dentry))
return lower_dentry;
if (!lower_dentry) {
/* We called vfs_path_lookup earlier, and did not get a negative
* dentry then. Don't confuse the lower filesystem by forcing
* one on it now...
*/
err = -ENOENT;
goto out;
}
lower_path.dentry = lower_dentry;
lower_path.mnt = mntget(lower_dir_mnt);
esdfs_set_lower_path(dentry, &lower_path);
/*
* If the intent is to create a file, then don't return an error, so
* the VFS will continue the process of making this negative dentry
* into a positive one.
*/
if (flags & (LOOKUP_CREATE|LOOKUP_RENAME_TARGET))
err = 0;
out:
if (err)
return ERR_PTR(err);
return ret_dentry;
}
struct dentry *esdfs_lookup(struct inode *dir, struct dentry *dentry,
unsigned int flags)
{
int err;
struct dentry *ret, *real_parent, *parent;
struct path lower_parent_path, old_lower_parent_path;
const struct cred *creds;
struct esdfs_sb_info *sbi = ESDFS_SB(dir->i_sb);
int use_dl;
parent = real_parent = dget_parent(dentry);
/* allocate dentry private data. We free it in ->d_release */
err = esdfs_new_dentry_private_data(dentry);
if (err) {
ret = ERR_PTR(err);
goto out;
}
if (ESDFS_DERIVE_PERMS(sbi)) {
err = esdfs_derived_lookup(dentry, &parent);
if (err) {
ret = ERR_PTR(err);
goto out;
}
}
esdfs_get_lower_path(parent, &lower_parent_path);
creds = esdfs_override_creds(ESDFS_SB(dir->i_sb),
ESDFS_I(d_inode(parent)), NULL);
if (!creds) {
ret = ERR_PTR(-EINVAL);
goto out_put;
}
/* Check if the lookup corresponds to the Download directory */
use_dl = esdfs_is_dl_lookup(dentry, parent);
ret = __esdfs_lookup(dentry, flags, &lower_parent_path,
ESDFS_I(dir)->userid,
use_dl);
if (IS_ERR(ret))
goto out_cred;
if (ret)
dentry = ret;
if (dentry->d_inode) {
fsstack_copy_attr_times(dentry->d_inode,
esdfs_lower_inode(dentry->d_inode));
esdfs_derive_lower_ownership(dentry, dentry->d_name.name);
}
/* update parent directory's atime */
fsstack_copy_attr_atime(parent->d_inode,
esdfs_lower_inode(parent->d_inode));
/*
* If this is a pseudo hard link, store the real parent and ensure
* that the link target directory contains any derived contents.
*/
if (parent != real_parent) {
esdfs_get_lower_path(real_parent, &old_lower_parent_path);
esdfs_set_lower_parent(dentry, old_lower_parent_path.dentry);
esdfs_put_lower_path(real_parent, &old_lower_parent_path);
esdfs_derive_mkdir_contents(dentry);
}
out_cred:
esdfs_revert_creds(creds, NULL);
out_put:
esdfs_put_lower_path(parent, &lower_parent_path);
out:
dput(parent);
if (parent != real_parent)
dput(real_parent);
return ret;
}