?? nfsfh.c
字號:
/* * linux/fs/nfsd/nfsfh.c * * NFS server file handle treatment. * * Copyright (C) 1995, 1996 Olaf Kirch <okir@monad.swb.de> * Portions Copyright (C) 1999 G. Allen Morris III <gam3@acm.org> * Extensive rewrite by Neil Brown <neilb@cse.unsw.edu.au> Southern-Spring 1999 */#include <linux/sched.h>#include <linux/slab.h>#include <linux/fs.h>#include <linux/unistd.h>#include <linux/string.h>#include <linux/stat.h>#include <linux/dcache.h>#include <asm/pgtable.h>#include <linux/sunrpc/svc.h>#include <linux/nfsd/nfsd.h>#define NFSDDBG_FACILITY NFSDDBG_FH#define NFSD_PARANOIA 1/* #define NFSD_DEBUG_VERBOSE 1 */static int nfsd_nr_verified;static int nfsd_nr_put;struct nfsd_getdents_callback { char *name; /* name that was found. It already points to a buffer NAME_MAX+1 is size */ unsigned long 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(void * __buf, const char * name, int len, loff_t pos, ino_t ino, unsigned int d_type){ struct nfsd_getdents_callback *buf = __buf; int result = 0; buf->sequence++;#ifdef NFSD_DEBUG_VERBOSEdprintk("filldir_one: seq=%d, ino=%ld, name=%s\n", buf->sequence, ino, name);#endif if (buf->ino == ino) { memcpy(buf->name, name, len); buf->name[len] = '\0'; buf->found = 1; result = -1; } return result;}/** * nfsd_get_name - default nfsd_operations->get_name function * @dentry: 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 nfsd_get_name(struct dentry *dentry, char *name, struct dentry *child){ struct inode *dir = dentry->d_inode; int error; struct file file; struct nfsd_getdents_callback buffer; error = -ENOTDIR; if (!dir || !S_ISDIR(dir->i_mode)) goto out; error = -EINVAL; if (!dir->i_fop) goto out; /* * Open the directory ... */ error = init_private_file(&file, dentry, FMODE_READ); if (error) goto out; error = -EINVAL; if (!file.f_op->readdir) goto out_close; buffer.name = name; buffer.ino = child->d_inode->i_ino; buffer.found = 0; buffer.sequence = 0; while (1) { int old_seq = buffer.sequence; error = vfs_readdir(&file, filldir_one, &buffer); if (error < 0) break; error = 0; if (buffer.found) break; error = -ENOENT; if (old_seq == buffer.sequence) break; }out_close: if (file.f_op->release) file.f_op->release(dir, &file);out: return error;}/* this should be provided by each filesystem in an nfsd_operations interface as * iget isn't really the right interface */static struct dentry *nfsd_iget(struct super_block *sb, unsigned long ino, __u32 generation){ /* iget isn't really right if the inode is currently unallocated!! * This should really all be done inside each filesystem * * ext2fs' read_inode has been strengthed to return a bad_inode if the inode * had been deleted. * * Currently we don't know the generation for parent directory, so a generation * of 0 means "accept any" */ struct inode *inode; struct list_head *lp; struct dentry *result; if (ino == 0) return ERR_PTR(-ESTALE); inode = iget(sb, ino); if (inode == NULL) return ERR_PTR(-ENOMEM); if (is_bad_inode(inode) || (generation && inode->i_generation != generation) ) { /* we didn't find the right inode.. */ dprintk("fh_verify: Inode %lu, Bad count: %d %d or version %u %u\n", inode->i_ino, inode->i_nlink, atomic_read(&inode->i_count), inode->i_generation, generation); iput(inode); return ERR_PTR(-ESTALE); } /* now to find a dentry. * If possible, get a well-connected one */ spin_lock(&dcache_lock); for (lp = inode->i_dentry.next; lp != &inode->i_dentry ; lp=lp->next) { result = list_entry(lp,struct dentry, d_alias); if (! (result->d_flags & DCACHE_NFSD_DISCONNECTED)) { dget_locked(result); result->d_vfs_flags |= DCACHE_REFERENCED; spin_unlock(&dcache_lock); iput(inode); return result; } } spin_unlock(&dcache_lock); result = d_alloc_root(inode); if (result == NULL) { iput(inode); return ERR_PTR(-ENOMEM); } result->d_flags |= DCACHE_NFSD_DISCONNECTED; return result;}static struct dentry *nfsd_get_dentry(struct super_block *sb, __u32 *fh, int len, int fhtype, int parent){ if (sb->s_op->fh_to_dentry) return sb->s_op->fh_to_dentry(sb, fh, len, fhtype, parent); switch (fhtype) { case 1: if (len < 2) break; if (parent) break; return nfsd_iget(sb, fh[0], fh[1]); case 2: if (len < 3) break; if (parent) return nfsd_iget(sb,fh[2],0); return nfsd_iget(sb,fh[0],fh[1]); default: break; } return ERR_PTR(-EINVAL);}/* this routine links an IS_ROOT dentry into the dcache tree. It gains "parent" * as a parent and "name" as a name * It should possibly go in dcache.c */int d_splice(struct dentry *target, struct dentry *parent, struct qstr *name){ struct dentry *tdentry;#ifdef NFSD_PARANOIA if (!IS_ROOT(target)) printk("nfsd: d_splice with no-root target: %s/%s\n", parent->d_name.name, name->name); if (!(target->d_flags & DCACHE_NFSD_DISCONNECTED)) printk("nfsd: d_splice with non-DISCONNECTED target: %s/%s\n", parent->d_name.name, name->name);#endif tdentry = d_alloc(parent, name); if (tdentry == NULL) return -ENOMEM; d_move(target, tdentry); /* tdentry will have been made a "child" of target (the parent of target) * make it an IS_ROOT instead */ spin_lock(&dcache_lock); list_del_init(&tdentry->d_child); tdentry->d_parent = tdentry; spin_unlock(&dcache_lock); d_rehash(target); dput(tdentry); /* if parent is properly connected, then we can assert that * the children are connected, but it must be a singluar (non-forking) * branch */ if (!(parent->d_flags & DCACHE_NFSD_DISCONNECTED)) { while (target) { target->d_flags &= ~DCACHE_NFSD_DISCONNECTED; parent = target; spin_lock(&dcache_lock); if (list_empty(&parent->d_subdirs)) target = NULL; else { target = list_entry(parent->d_subdirs.next, struct dentry, d_child);#ifdef NFSD_PARANOIA /* must be only child */ if (target->d_child.next != &parent->d_subdirs || target->d_child.prev != &parent->d_subdirs) printk("nfsd: d_splice found non-singular disconnected branch: %s/%s\n", parent->d_name.name, target->d_name.name);#endif } spin_unlock(&dcache_lock); } } return 0;}/* this routine finds the dentry of the parent of a given directory * it should be in the filesystem accessed by nfsd_operations * it assumes lookup("..") works. */struct dentry *nfsd_findparent(struct dentry *child){ struct dentry *tdentry, *pdentry; tdentry = d_alloc(child, &(const struct qstr) {"..", 2, 0}); if (!tdentry) return ERR_PTR(-ENOMEM); /* I'm going to assume that if the returned dentry is different, then * it is well connected. But nobody returns different dentrys do they? */ pdentry = child->d_inode->i_op->lookup(child->d_inode, tdentry); d_drop(tdentry); /* we never want ".." hashed */ if (!pdentry && tdentry->d_inode == NULL) { /* File system cannot find ".." ... sad but possible */ pdentry = ERR_PTR(-EINVAL); } if (!pdentry) { /* I don't want to return a ".." dentry. * I would prefer to return an unconnected "IS_ROOT" dentry, * though a properly connected dentry is even better */ /* if first or last of alias list is not tdentry, use that * else make a root dentry */ struct list_head *aliases = &tdentry->d_inode->i_dentry; spin_lock(&dcache_lock); if (aliases->next != aliases) { pdentry = list_entry(aliases->next, struct dentry, d_alias); if (pdentry == tdentry) pdentry = list_entry(aliases->prev, struct dentry, d_alias); if (pdentry == tdentry) pdentry = NULL; if (pdentry) dget_locked(pdentry); } spin_unlock(&dcache_lock); if (pdentry == NULL) { pdentry = d_alloc_root(tdentry->d_inode); if (pdentry) { igrab(tdentry->d_inode); pdentry->d_flags |= DCACHE_NFSD_DISCONNECTED; } } if (pdentry == NULL) pdentry = ERR_PTR(-ENOMEM); } dput(tdentry); /* it is not hashed, it will be discarded */ return pdentry;}static struct dentry *splice(struct dentry *child, struct dentry *parent){ int err = 0, nerr; struct qstr qs; char namebuf[256]; struct list_head *lp; /* child is an IS_ROOT (anonymous) dentry, but it is hypothesised that * it should be a child of parent. * We see if we can find a name and, if we can - splice it in. * We lookup the name before locking (i_sem) the directory as namelookup * also claims i_sem. If the name gets changed then we will loop around * and try again in find_fh_dentry. */ nerr = nfsd_get_name(parent, namebuf, child); /* * We now claim the parent i_sem so that no-one else tries to create * a dentry in the parent while we are. */ down(&parent->d_inode->i_sem); /* Now, things might have changed while we waited. * Possibly a friendly filesystem found child and spliced it in in response * to a lookup (though nobody does this yet). In this case, just succeed. */ if (child->d_parent == parent) goto out; /* Possibly a new dentry has been made for this child->d_inode in * parent by a lookup. In this case return that dentry. Caller must * notice and act accordingly */ spin_lock(&dcache_lock); list_for_each(lp, &child->d_inode->i_dentry) { struct dentry *tmp = list_entry(lp,struct dentry, d_alias); if (!list_empty(&tmp->d_hash) && tmp->d_parent == parent) { child = dget_locked(tmp); spin_unlock(&dcache_lock); goto out; } } spin_unlock(&dcache_lock); /* now we need that name. If there was an error getting it, now is th * time to bail out. */ if ((err = nerr)) goto out; qs.name = namebuf; qs.len = strlen(namebuf); if (find_inode_number(parent, &qs) != 0) { /* Now that IS odd. I wonder what it means... */ err = -EEXIST; printk("nfsd-fh: found a name that I didn't expect: %s/%s\n", parent->d_name.name, qs.name); goto out; } err = d_splice(child, parent, &qs); dprintk("nfsd_fh: found name %s for ino %ld\n", child->d_name.name, child->d_inode->i_ino); out: up(&parent->d_inode->i_sem); if (err) return ERR_PTR(err); else return child;}/* * This is the basic lookup mechanism for turning an NFS file handle * into a dentry. * We use nfsd_iget and if that doesn't return a suitably connected dentry, * we try to find the parent, and the parent of that and so-on until a * connection if made. */static struct dentry *find_fh_dentry(struct super_block *sb, __u32 *datap, int len, int fhtype, int needpath){ struct dentry *dentry, *result = NULL; struct dentry *tmp; int err = -ESTALE; /* the sb->s_nfsd_free_path_sem semaphore is needed to make sure that only one unconnected (free) * dcache path ever exists, as otherwise two partial paths might get * joined together, which would be very confusing. * If there is ever an unconnected non-root directory, then this lock * must be held. */ nfsdstats.fh_lookup++; /* * Attempt to find the inode. */ retry: down(&sb->s_nfsd_free_path_sem); result = nfsd_get_dentry(sb, datap, len, fhtype, 0); if (IS_ERR(result) || !(result->d_flags & DCACHE_NFSD_DISCONNECTED) || (!S_ISDIR(result->d_inode->i_mode) && ! needpath)) { up(&sb->s_nfsd_free_path_sem); err = PTR_ERR(result); if (IS_ERR(result)) goto err_out; if ((result->d_flags & DCACHE_NFSD_DISCONNECTED)) nfsdstats.fh_anon++; return result; } /* It's a directory, or we are required to confirm the file's * location in the tree. */ dprintk("nfs_fh: need to look harder for %d/%d\n",sb->s_dev,datap[0]); if (!S_ISDIR(result->d_inode->i_mode)) { nfsdstats.fh_nocache_nondir++; /* need to iget dirino and make sure this inode is in that directory */ dentry = nfsd_get_dentry(sb, datap, len, fhtype, 1); err = PTR_ERR(dentry); if (IS_ERR(dentry)) goto err_result; err = -ESTALE; if (!dentry->d_inode || !S_ISDIR(dentry->d_inode->i_mode)) { goto err_dentry; } tmp = splice(result, dentry); err = PTR_ERR(tmp); if (IS_ERR(tmp)) goto err_dentry; if (tmp != result) { /* it is safe to just use tmp instead, but we must discard result first */ d_drop(result); dput(result); result = tmp; } } else { nfsdstats.fh_nocache_dir++;
?? 快捷鍵說明
復制代碼
Ctrl + C
搜索代碼
Ctrl + F
全屏模式
F11
切換主題
Ctrl + Shift + D
顯示快捷鍵
?
增大字號
Ctrl + =
減小字號
Ctrl + -