cregit-Linux how code gets into the kernel

Release 4.11 fs/exportfs/expfs.c

Directory: fs/exportfs
/*
 * 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

PersonTokensPropCommitsCommitProp
Christoph Hellwig6578.31%375.00%
Al Viro1821.69%125.00%
Total83100.00%4100.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

PersonTokensPropCommitsCommitProp
Christoph Hellwig12477.02%228.57%
Nicholas Piggin3521.74%342.86%
Al Viro21.24%228.57%
Total161100.00%7100.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

PersonTokensPropCommitsCommitProp
J. Bruce Fields70100.00%1100.00%
Total70100.00%1100.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

PersonTokensPropCommitsCommitProp
J. Bruce Fields81100.00%1100.00%
Total81100.00%1100.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

PersonTokensPropCommitsCommitProp
J. Bruce Fields12041.81%215.38%
Neil Brown11941.46%323.08%
Christoph Hellwig3512.20%323.08%
Al Viro82.79%323.08%
Harvey Harrison41.39%17.69%
Andrew Morton10.35%17.69%
Total287100.00%13100.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

PersonTokensPropCommitsCommitProp
J. Bruce Fields11190.24%466.67%
Neil Brown86.50%116.67%
Christoph Hellwig43.25%116.67%
Total123100.00%6100.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

PersonTokensPropCommitsCommitProp
Neil Brown8583.33%125.00%
Miklos Szeredi1211.76%125.00%
Al Viro43.92%125.00%
David Howells10.98%125.00%
Total102100.00%4100.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

PersonTokensPropCommitsCommitProp
Neil Brown18062.94%215.38%
J. Bruce Fields5017.48%17.69%
Al Viro3913.64%646.15%
David Howells155.24%215.38%
Christoph Hellwig10.35%17.69%
Stephen D. Smalley10.35%17.69%
Total286100.00%13100.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

PersonTokensPropCommitsCommitProp
Neil Brown8158.27%120.00%
Christoph Hellwig2517.99%120.00%
Aneesh Kumar K.V2417.27%120.00%
Al Viro75.04%120.00%
Namjae Jeon21.44%120.00%
Total139100.00%5100.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

PersonTokensPropCommitsCommitProp
Cyrill V. Gorcunov4663.89%125.00%
Christoph Hellwig2636.11%375.00%
Total72100.00%4100.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

PersonTokensPropCommitsCommitProp
Al Viro5658.95%120.00%
Cyrill V. Gorcunov2223.16%120.00%
Christoph Hellwig1717.89%360.00%
Total95100.00%5100.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

PersonTokensPropCommitsCommitProp
Christoph Hellwig38283.77%650.00%
Neil Brown286.14%18.33%
Aneesh Kumar K.V173.73%18.33%
Al Viro143.07%216.67%
J. Bruce Fields143.07%18.33%
David Howells10.22%18.33%
Total456100.00%12100.00%

EXPORT_SYMBOL_GPL(exportfs_decode_fh); MODULE_LICENSE("GPL");

Overall Contributors

PersonTokensPropCommitsCommitProp
Christoph Hellwig71434.63%1221.05%
Neil Brown54426.38%610.53%
J. Bruce Fields45021.82%814.04%
Al Viro1557.52%1322.81%
Cyrill V. Gorcunov733.54%11.75%
Aneesh Kumar K.V411.99%23.51%
Nicholas Piggin351.70%35.26%
David Howells200.97%47.02%
Miklos Szeredi120.58%11.75%
Dave Jones50.24%11.75%
Harvey Harrison40.19%11.75%
Ingo Molnar30.15%11.75%
Fabian Frederick20.10%11.75%
Namjae Jeon20.10%11.75%
Stephen D. Smalley10.05%11.75%
Andrew Morton10.05%11.75%
Total2062100.00%57100.00%
Directory: fs/exportfs
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with cregit.