Release 4.11 fs/exportfs/expfs.c
/*
* Copyright (C) Neil Brown 2002
* Copyright (C) Christoph Hellwig 2007
*
* This file contains the code mapping from inodes to NFS file handles,
* and for mapping back from file handles to dentries.
*
* For details on why we do all the strange and hairy things in here
* take a look at Documentation/filesystems/nfs/Exporting.
*/
#include <linux/exportfs.h>
#include <linux/fs.h>
#include <linux/file.h>
#include <linux/module.h>
#include <linux/mount.h>
#include <linux/namei.h>
#include <linux/sched.h>
#include <linux/cred.h>
#define dprintk(fmt, args...) do{}while(0)
static int get_name(const struct path *path, char *name, struct dentry *child);
static int exportfs_get_name(struct vfsmount *mnt, struct dentry *dir,
char *name, struct dentry *child)
{
const struct export_operations *nop = dir->d_sb->s_export_op;
struct path path = {.mnt = mnt, .dentry = dir};
if (nop->get_name)
return nop->get_name(dir, name, child);
else
return get_name(&path, name, child);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Christoph Hellwig | 65 | 78.31% | 3 | 75.00% |
Al Viro | 18 | 21.69% | 1 | 25.00% |
Total | 83 | 100.00% | 4 | 100.00% |
/*
* Check if the dentry or any of it's aliases is acceptable.
*/
static struct dentry *
find_acceptable_alias(struct dentry *result,
int (*acceptable)(void *context, struct dentry *dentry),
void *context)
{
struct dentry *dentry, *toput = NULL;
struct inode *inode;
if (acceptable(context, result))
return result;
inode = result->d_inode;
spin_lock(&inode->i_lock);
hlist_for_each_entry(dentry, &inode->i_dentry, d_u.d_alias) {
dget(dentry);
spin_unlock(&inode->i_lock);
if (toput)
dput(toput);
if (dentry != result && acceptable(context, dentry)) {
dput(result);
return dentry;
}
spin_lock(&inode->i_lock);
toput = dentry;
}
spin_unlock(&inode->i_lock);
if (toput)
dput(toput);
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Christoph Hellwig | 124 | 77.02% | 2 | 28.57% |
Nicholas Piggin | 35 | 21.74% | 3 | 42.86% |
Al Viro | 2 | 1.24% | 2 | 28.57% |
Total | 161 | 100.00% | 7 | 100.00% |
static bool dentry_connected(struct dentry *dentry)
{
dget(dentry);
while (dentry->d_flags & DCACHE_DISCONNECTED) {
struct dentry *parent = dget_parent(dentry);
dput(dentry);
if (IS_ROOT(dentry)) {
dput(parent);
return false;
}
dentry = parent;
}
dput(dentry);
return true;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
J. Bruce Fields | 70 | 100.00% | 1 | 100.00% |
Total | 70 | 100.00% | 1 | 100.00% |
static void clear_disconnected(struct dentry *dentry)
{
dget(dentry);
while (dentry->d_flags & DCACHE_DISCONNECTED) {
struct dentry *parent = dget_parent(dentry);
WARN_ON_ONCE(IS_ROOT(dentry));
spin_lock(&dentry->d_lock);
dentry->d_flags &= ~DCACHE_DISCONNECTED;
spin_unlock(&dentry->d_lock);
dput(dentry);
dentry = parent;
}
dput(dentry);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
J. Bruce Fields | 81 | 100.00% | 1 | 100.00% |
Total | 81 | 100.00% | 1 | 100.00% |
/*
* Reconnect a directory dentry with its parent.
*
* This can return a dentry, or NULL, or an error.
*
* In the first case the returned dentry is the parent of the given
* dentry, and may itself need to be reconnected to its parent.
*
* In the NULL case, a concurrent VFS operation has either renamed or
* removed this directory. The concurrent operation has reconnected our
* dentry, so we no longer need to.
*/
static struct dentry *reconnect_one(struct vfsmount *mnt,
struct dentry *dentry, char *nbuf)
{
struct dentry *parent;
struct dentry *tmp;
int err;
parent = ERR_PTR(-EACCES);
inode_lock(dentry->d_inode);
if (mnt->mnt_sb->s_export_op->get_parent)
parent = mnt->mnt_sb->s_export_op->get_parent(dentry);
inode_unlock(dentry->d_inode);
if (IS_ERR(parent)) {
dprintk("%s: get_parent of %ld failed, err %d\n",
__func__, dentry->d_inode->i_ino, PTR_ERR(parent));
return parent;
}
dprintk("%s: find name of %lu in %lu\n", __func__,
dentry->d_inode->i_ino, parent->d_inode->i_ino);
err = exportfs_get_name(mnt, parent, nbuf, dentry);
if (err == -ENOENT)
goto out_reconnected;
if (err)
goto out_err;
dprintk("%s: found name: %s\n", __func__, nbuf);
tmp = lookup_one_len_unlocked(nbuf, parent, strlen(nbuf));
if (IS_ERR(tmp)) {
dprintk("%s: lookup failed: %d\n", __func__, PTR_ERR(tmp));
goto out_err;
}
if (tmp != dentry) {
/*
* Somebody has renamed it since exportfs_get_name();
* great, since it could've only been renamed if it
* got looked up and thus connected, and it would
* remain connected afterwards. We are done.
*/
dput(tmp);
goto out_reconnected;
}
dput(tmp);
if (IS_ROOT(dentry)) {
err = -ESTALE;
goto out_err;
}
return parent;
out_err:
dput(parent);
return ERR_PTR(err);
out_reconnected:
dput(parent);
/*
* Someone must have renamed our entry into another parent, in
* which case it has been reconnected by the rename.
*
* Or someone removed it entirely, in which case filehandle
* lookup will succeed but the directory is now IS_DEAD and
* subsequent operations on it will fail.
*
* Alternatively, maybe there was no race at all, and the
* filesystem is just corrupt and gave us a parent that doesn't
* actually contain any entry pointing to this inode. So,
* double check that this worked and return -ESTALE if not:
*/
if (!dentry_connected(dentry))
return ERR_PTR(-ESTALE);
return NULL;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
J. Bruce Fields | 120 | 41.81% | 2 | 15.38% |
Neil Brown | 119 | 41.46% | 3 | 23.08% |
Christoph Hellwig | 35 | 12.20% | 3 | 23.08% |
Al Viro | 8 | 2.79% | 3 | 23.08% |
Harvey Harrison | 4 | 1.39% | 1 | 7.69% |
Andrew Morton | 1 | 0.35% | 1 | 7.69% |
Total | 287 | 100.00% | 13 | 100.00% |
/*
* Make sure target_dir is fully connected to the dentry tree.
*
* On successful return, DCACHE_DISCONNECTED will be cleared on
* target_dir, and target_dir->d_parent->...->d_parent will reach the
* root of the filesystem.
*
* Whenever DCACHE_DISCONNECTED is unset, target_dir is fully connected.
* But the converse is not true: target_dir may have DCACHE_DISCONNECTED
* set but already be connected. In that case we'll verify the
* connection to root and then clear the flag.
*
* Note that target_dir could be removed by a concurrent operation. In
* that case reconnect_path may still succeed with target_dir fully
* connected, but further operations using the filehandle will fail when
* necessary (due to S_DEAD being set on the directory).
*/
static int
reconnect_path(struct vfsmount *mnt, struct dentry *target_dir, char *nbuf)
{
struct dentry *dentry, *parent;
dentry = dget(target_dir);
while (dentry->d_flags & DCACHE_DISCONNECTED) {
BUG_ON(dentry == mnt->mnt_sb->s_root);
if (IS_ROOT(dentry))
parent = reconnect_one(mnt, dentry, nbuf);
else
parent = dget_parent(dentry);
if (!parent)
break;
dput(dentry);
if (IS_ERR(parent))
return PTR_ERR(parent);
dentry = parent;
}
dput(dentry);
clear_disconnected(target_dir);
return 0;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
J. Bruce Fields | 111 | 90.24% | 4 | 66.67% |
Neil Brown | 8 | 6.50% | 1 | 16.67% |
Christoph Hellwig | 4 | 3.25% | 1 | 16.67% |
Total | 123 | 100.00% | 6 | 100.00% |
struct getdents_callback {
struct dir_context ctx;
char *name; /* name that was found. It already points to a
buffer NAME_MAX+1 is size */
u64 ino; /* the inum we are looking for */
int found; /* inode matched? */
int sequence; /* sequence counter */
};
/*
* A rather strange filldir function to capture
* the name matching the specified inode number.
*/
static int filldir_one(struct dir_context *ctx, const char *name, int len,
loff_t pos, u64 ino, unsigned int d_type)
{
struct getdents_callback *buf =
container_of(ctx, struct getdents_callback, ctx);
int result = 0;
buf->sequence++;
if (buf->ino == ino && len <= NAME_MAX) {
memcpy(buf->name, name, len);
buf->name[len] = '\0';
buf->found = 1;
result = -1;
}
return result;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Neil Brown | 85 | 83.33% | 1 | 25.00% |
Miklos Szeredi | 12 | 11.76% | 1 | 25.00% |
Al Viro | 4 | 3.92% | 1 | 25.00% |
David Howells | 1 | 0.98% | 1 | 25.00% |
Total | 102 | 100.00% | 4 | 100.00% |
/**
* get_name - default export_operations->get_name function
* @path: the directory in which to find a name
* @name: a pointer to a %NAME_MAX+1 char buffer to store the name
* @child: the dentry for the child directory.
*
* calls readdir on the parent until it finds an entry with
* the same inode number as the child, and returns that.
*/
static int get_name(const struct path *path, char *name, struct dentry *child)
{
const struct cred *cred = current_cred();
struct inode *dir = path->dentry->d_inode;
int error;
struct file *file;
struct kstat stat;
struct path child_path = {
.mnt = path->mnt,
.dentry = child,
};
struct getdents_callback buffer = {
.ctx.actor = filldir_one,
.name = name,
};
error = -ENOTDIR;
if (!dir || !S_ISDIR(dir->i_mode))
goto out;
error = -EINVAL;
if (!dir->i_fop)
goto out;
/*
* inode->i_ino is unsigned long, kstat->ino is u64, so the
* former would be insufficient on 32-bit hosts when the
* filesystem supports 64-bit inode numbers. So we need to
* actually call ->getattr, not just read i_ino:
*/
error = vfs_getattr_nosec(&child_path, &stat,
STATX_INO, AT_STATX_SYNC_AS_STAT);
if (error)
return error;
buffer.ino = stat.ino;
/*
* Open the directory ...
*/
file = dentry_open(path, O_RDONLY, cred);
error = PTR_ERR(file);
if (IS_ERR(file))
goto out;
error = -EINVAL;
if (!file->f_op->iterate && !file->f_op->iterate_shared)
goto out_close;
buffer.sequence = 0;
while (1) {
int old_seq = buffer.sequence;
error = iterate_dir(file, &buffer.ctx);
if (buffer.found) {
error = 0;
break;
}
if (error < 0)
break;
error = -ENOENT;
if (old_seq == buffer.sequence)
break;
}
out_close:
fput(file);
out:
return error;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Neil Brown | 180 | 62.94% | 2 | 15.38% |
J. Bruce Fields | 50 | 17.48% | 1 | 7.69% |
Al Viro | 39 | 13.64% | 6 | 46.15% |
David Howells | 15 | 5.24% | 2 | 15.38% |
Christoph Hellwig | 1 | 0.35% | 1 | 7.69% |
Stephen D. Smalley | 1 | 0.35% | 1 | 7.69% |
Total | 286 | 100.00% | 13 | 100.00% |
/**
* export_encode_fh - default export_operations->encode_fh function
* @inode: the object to encode
* @fid: where to store the file handle fragment
* @max_len: maximum length to store there
* @parent: parent directory inode, if wanted
*
* This default encode_fh function assumes that the 32 inode number
* is suitable for locating an inode, and that the generation number
* can be used to check that it is still valid. It places them in the
* filehandle fragment where export_decode_fh expects to find them.
*/
static int export_encode_fh(struct inode *inode, struct fid *fid,
int *max_len, struct inode *parent)
{
int len = *max_len;
int type = FILEID_INO32_GEN;
if (parent && (len < 4)) {
*max_len = 4;
return FILEID_INVALID;
} else if (len < 2) {
*max_len = 2;
return FILEID_INVALID;
}
len = 2;
fid->i32.ino = inode->i_ino;
fid->i32.gen = inode->i_generation;
if (parent) {
fid->i32.parent_ino = parent->i_ino;
fid->i32.parent_gen = parent->i_generation;
len = 4;
type = FILEID_INO32_GEN_PARENT;
}
*max_len = len;
return type;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Neil Brown | 81 | 58.27% | 1 | 20.00% |
Christoph Hellwig | 25 | 17.99% | 1 | 20.00% |
Aneesh Kumar K.V | 24 | 17.27% | 1 | 20.00% |
Al Viro | 7 | 5.04% | 1 | 20.00% |
Namjae Jeon | 2 | 1.44% | 1 | 20.00% |
Total | 139 | 100.00% | 5 | 100.00% |
int exportfs_encode_inode_fh(struct inode *inode, struct fid *fid,
int *max_len, struct inode *parent)
{
const struct export_operations *nop = inode->i_sb->s_export_op;
if (nop && nop->encode_fh)
return nop->encode_fh(inode, fid->raw, max_len, parent);
return export_encode_fh(inode, fid, max_len, parent);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Cyrill V. Gorcunov | 46 | 63.89% | 1 | 25.00% |
Christoph Hellwig | 26 | 36.11% | 3 | 75.00% |
Total | 72 | 100.00% | 4 | 100.00% |
EXPORT_SYMBOL_GPL(exportfs_encode_inode_fh);
int exportfs_encode_fh(struct dentry *dentry, struct fid *fid, int *max_len,
int connectable)
{
int error;
struct dentry *p = NULL;
struct inode *inode = dentry->d_inode, *parent = NULL;
if (connectable && !S_ISDIR(inode->i_mode)) {
p = dget_parent(dentry);
/*
* note that while p might've ceased to be our parent already,
* it's still pinned by and still positive.
*/
parent = p->d_inode;
}
error = exportfs_encode_inode_fh(inode, fid, max_len, parent);
dput(p);
return error;
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Al Viro | 56 | 58.95% | 1 | 20.00% |
Cyrill V. Gorcunov | 22 | 23.16% | 1 | 20.00% |
Christoph Hellwig | 17 | 17.89% | 3 | 60.00% |
Total | 95 | 100.00% | 5 | 100.00% |
EXPORT_SYMBOL_GPL(exportfs_encode_fh);
struct dentry *exportfs_decode_fh(struct vfsmount *mnt, struct fid *fid,
int fh_len, int fileid_type,
int (*acceptable)(void *, struct dentry *), void *context)
{
const struct export_operations *nop = mnt->mnt_sb->s_export_op;
struct dentry *result, *alias;
char nbuf[NAME_MAX+1];
int err;
/*
* Try to get any dentry for the given file handle from the filesystem.
*/
if (!nop || !nop->fh_to_dentry)
return ERR_PTR(-ESTALE);
result = nop->fh_to_dentry(mnt->mnt_sb, fid, fh_len, fileid_type);
if (PTR_ERR(result) == -ENOMEM)
return ERR_CAST(result);
if (IS_ERR_OR_NULL(result))
return ERR_PTR(-ESTALE);
if (d_is_dir(result)) {
/*
* This request is for a directory.
*
* On the positive side there is only one dentry for each
* directory inode. On the negative side this implies that we
* to ensure our dentry is connected all the way up to the
* filesystem root.
*/
if (result->d_flags & DCACHE_DISCONNECTED) {
err = reconnect_path(mnt, result, nbuf);
if (err)
goto err_result;
}
if (!acceptable(context, result)) {
err = -EACCES;
goto err_result;
}
return result;
} else {
/*
* It's not a directory. Life is a little more complicated.
*/
struct dentry *target_dir, *nresult;
/*
* See if either the dentry we just got from the filesystem
* or any alias for it is acceptable. This is always true
* if this filesystem is exported without the subtreecheck
* option. If the filesystem is exported with the subtree
* check option there's a fair chance we need to look at
* the parent directory in the file handle and make sure
* it's connected to the filesystem root.
*/
alias = find_acceptable_alias(result, acceptable, context);
if (alias)
return alias;
/*
* Try to extract a dentry for the parent directory from the
* file handle. If this fails we'll have to give up.
*/
err = -ESTALE;
if (!nop->fh_to_parent)
goto err_result;
target_dir = nop->fh_to_parent(mnt->mnt_sb, fid,
fh_len, fileid_type);
if (!target_dir)
goto err_result;
err = PTR_ERR(target_dir);
if (IS_ERR(target_dir))
goto err_result;
/*
* And as usual we need to make sure the parent directory is
* connected to the filesystem root. The VFS really doesn't
* like disconnected directories..
*/
err = reconnect_path(mnt, target_dir, nbuf);
if (err) {
dput(target_dir);
goto err_result;
}
/*
* Now that we've got both a well-connected parent and a
* dentry for the inode we're after, make sure that our
* inode is actually connected to the parent.
*/
err = exportfs_get_name(mnt, target_dir, nbuf, result);
if (!err) {
inode_lock(target_dir->d_inode);
nresult = lookup_one_len(nbuf, target_dir,
strlen(nbuf));
inode_unlock(target_dir->d_inode);
if (!IS_ERR(nresult)) {
if (nresult->d_inode) {
dput(result);
result = nresult;
} else
dput(nresult);
}
}
/*
* At this point we are done with the parent, but it's pinned
* by the child dentry anyway.
*/
dput(target_dir);
/*
* And finally make sure the dentry is actually acceptable
* to NFSD.
*/
alias = find_acceptable_alias(result, acceptable, context);
if (!alias) {
err = -EACCES;
goto err_result;
}
return alias;
}
err_result:
dput(result);
if (err != -ENOMEM)
err = -ESTALE;
return ERR_PTR(err);
}
Contributors
Person | Tokens | Prop | Commits | CommitProp |
Christoph Hellwig | 382 | 83.77% | 6 | 50.00% |
Neil Brown | 28 | 6.14% | 1 | 8.33% |
Aneesh Kumar K.V | 17 | 3.73% | 1 | 8.33% |
Al Viro | 14 | 3.07% | 2 | 16.67% |
J. Bruce Fields | 14 | 3.07% | 1 | 8.33% |
David Howells | 1 | 0.22% | 1 | 8.33% |
Total | 456 | 100.00% | 12 | 100.00% |
EXPORT_SYMBOL_GPL(exportfs_decode_fh);
MODULE_LICENSE("GPL");
Overall Contributors
Person | Tokens | Prop | Commits | CommitProp |
Christoph Hellwig | 714 | 34.63% | 12 | 21.05% |
Neil Brown | 544 | 26.38% | 6 | 10.53% |
J. Bruce Fields | 450 | 21.82% | 8 | 14.04% |
Al Viro | 155 | 7.52% | 13 | 22.81% |
Cyrill V. Gorcunov | 73 | 3.54% | 1 | 1.75% |
Aneesh Kumar K.V | 41 | 1.99% | 2 | 3.51% |
Nicholas Piggin | 35 | 1.70% | 3 | 5.26% |
David Howells | 20 | 0.97% | 4 | 7.02% |
Miklos Szeredi | 12 | 0.58% | 1 | 1.75% |
Dave Jones | 5 | 0.24% | 1 | 1.75% |
Harvey Harrison | 4 | 0.19% | 1 | 1.75% |
Ingo Molnar | 3 | 0.15% | 1 | 1.75% |
Fabian Frederick | 2 | 0.10% | 1 | 1.75% |
Namjae Jeon | 2 | 0.10% | 1 | 1.75% |
Stephen D. Smalley | 1 | 0.05% | 1 | 1.75% |
Andrew Morton | 1 | 0.05% | 1 | 1.75% |
Total | 2062 | 100.00% | 57 | 100.00% |
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.