📄 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/malloc.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 { struct qstr *name; /* name that was found. name->name already points to a buffer */ 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, off_t pos, ino_t ino, unsigned int d_type){ struct nfsd_getdents_callback *buf = __buf; struct qstr *qs = buf->name; char *nbuf = (char*)qs->name; /* cast is to get rid of "const" */ 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) { qs->len = len; memcpy(nbuf, name, len); nbuf[len] = '\0'; buf->found = 1; result = -1; } return result;}/* * Read a directory and return the name of the specified entry. * i_sem is already down(). * The whole thing is a total BS. It should not be done via readdir(), damnit! * Oh, well, as soon as it will be in filesystems... */static int get_ino_name(struct dentry *dentry, struct qstr *name, unsigned long ino){ 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 = ino; buffer.found = 0; buffer.sequence = 0; while (1) { int old_seq = buffer.sequence; error = file.f_op->readdir(&file, &buffer, filldir_one); 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; inode = iget(sb, ino); 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); 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; d_rehash(result); /* so a dput won't loose it */ return result;}/* 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 name->hash = full_name_hash(name->name, name->len); 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(&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) { /* 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(igrab(tdentry->d_inode)); if (pdentry) { pdentry->d_flags |= DCACHE_NFSD_DISCONNECTED; d_rehash(pdentry); } } 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; struct qstr qs; char namebuf[256]; struct list_head *lp; struct dentry *tmp; /* 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 hold the i_sem on the parent the whole time to try to follow locking protocols. */ qs.name = namebuf; 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); for (lp = child->d_inode->i_dentry.next; lp != &child->d_inode->i_dentry ; lp=lp->next) { tmp = list_entry(lp,struct dentry, d_alias); if (tmp->d_parent == parent) { child = dget_locked(tmp); spin_unlock(&dcache_lock); goto out; } } spin_unlock(&dcache_lock); /* well, if we can find a name for child in parent, it should be safe to splice it in */ err = get_ino_name(parent, &qs, child->d_inode->i_ino); if (err) goto out; tmp = d_lookup(parent, &qs); if (tmp) { /* 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); dput(tmp); 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, ino_t ino, int generation, ino_t dirino, int needpath){ struct dentry *dentry, *result = NULL; struct dentry *tmp; int found =0; 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_iget(sb, ino, generation); 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/%ld\n",sb->s_dev,ino); found = 0; if (!S_ISDIR(result->d_inode->i_mode)) { nfsdstats.fh_nocache_nondir++; if (dirino == 0) goto err_result; /* don't know how to find parent */ else { /* need to iget dirino and make sure this inode is in that directory */ dentry = nfsd_iget(sb, dirino, 0); 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; } if (!(dentry->d_flags & DCACHE_NFSD_DISCONNECTED)) found = 1; 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;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -