/* * linux/fs/nfsd/nfsfh.c * * NFS server file handle treatment. * * Copyright (C) 1995, 1996 Olaf Kirch * Portions Copyright (C) 1999 G. Allen Morris III * Extensive rewrite by Neil Brown Southern-Spring 1999 */ #include #include #include #include #include #include #include #include #include #include #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_VERBOSE dprintk("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++; dentry = dget(result); } while(dentry->d_flags & DCACHE_NFSD_DISCONNECTED) { /* LOOP INVARIANT */ /* haven't found a place in the tree yet, but we do have a free path * from dentry down to result, and dentry is a directory. * Have a hold on dentry and result */ struct dentry *pdentry; struct inode *parent; pdentry = nfsd_findparent(dentry); err = PTR_ERR(pdentry); if (IS_ERR(pdentry)) goto err_dentry; parent = pdentry->d_inode; err = -EACCES; if (!parent) { dput(pdentry); goto err_dentry; } tmp = splice(dentry, pdentry); if (tmp != dentry) { /* Something wrong. We need to drop the whole dentry->result path * whatever it was */ struct dentry *d; for (d=result ; d ; d=(d->d_parent == d)?NULL:d->d_parent) d_drop(d); } if (IS_ERR(tmp)) { err = PTR_ERR(tmp); dput(pdentry); goto err_dentry; } if (tmp != dentry) { /* we lost a race, try again */ dput(pdentry); dput(tmp); dput(dentry); dput(result); /* this will discard the whole free path, so we can up the semaphore */ up(&sb->s_nfsd_free_path_sem); goto retry; } dput(dentry); dentry = pdentry; } dput(dentry); up(&sb->s_nfsd_free_path_sem); return result; err_dentry: dput(dentry); err_result: dput(result); up(&sb->s_nfsd_free_path_sem); err_out: if (err == -ESTALE) nfsdstats.fh_stale++; return ERR_PTR(err); } /* * Perform sanity checks on the dentry in a client's file handle. * * Note that the file handle dentry may need to be freed even after * an error return. * * This is only called at the start of an nfsproc call, so fhp points to * a svc_fh which is all 0 except for the over-the-wire file handle. */ u32 fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) { struct knfsd_fh *fh = &fhp->fh_handle; struct svc_export *exp; struct dentry *dentry; struct inode *inode; u32 error = 0; dprintk("nfsd: fh_verify(%s)\n", SVCFH_fmt(fhp)); if (!fhp->fh_dentry) { kdev_t xdev; ino_t xino; __u32 *datap=NULL; int data_left = fh->fh_size/4; int nfsdev; error = nfserr_stale; if (rqstp->rq_vers == 3) error = nfserr_badhandle; if (fh->fh_version == 1) { datap = fh->fh_auth; if (--data_left<0) goto out; switch (fh->fh_auth_type) { case 0: break; default: goto out; } switch (fh->fh_fsid_type) { case 0: if ((data_left-=2)<0) goto out; nfsdev = ntohl(*datap++); xdev = MKDEV(nfsdev>>16, nfsdev&0xFFFF); xino = *datap++; break; default: goto out; } } else { if (fh->fh_size != NFS_FHSIZE) goto out; /* assume old filehandle format */ xdev = u32_to_kdev_t(fh->ofh_xdev); xino = u32_to_ino_t(fh->ofh_xino); } /* * Look up the export entry. */ error = nfserr_stale; exp = exp_get(rqstp->rq_client, xdev, xino); if (!exp) { /* export entry revoked */ nfsdstats.fh_stale++; goto out; } /* Check if the request originated from a secure port. */ error = nfserr_perm; if (!rqstp->rq_secure && EX_SECURE(exp)) { printk(KERN_WARNING "nfsd: request from insecure port (%08x:%d)!\n", ntohl(rqstp->rq_addr.sin_addr.s_addr), ntohs(rqstp->rq_addr.sin_port)); goto out; } /* Set user creds if we haven't done so already. */ nfsd_setuser(rqstp, exp); /* * Look up the dentry using the NFS file handle. */ error = nfserr_stale; if (rqstp->rq_vers == 3) error = nfserr_badhandle; if (fh->fh_version == 1) { /* if fileid_type != 0, and super_operations provide fh_to_dentry lookup, * then should use that */ switch (fh->fh_fileid_type) { case 0: dentry = dget(exp->ex_dentry); break; default: dentry = find_fh_dentry(exp->ex_dentry->d_inode->i_sb, datap, data_left, fh->fh_fileid_type, !(exp->ex_flags & NFSEXP_NOSUBTREECHECK)); } } else { __u32 tfh[3]; tfh[0] = fh->ofh_ino; tfh[1] = fh->ofh_generation; tfh[2] = fh->ofh_dirino; dentry = find_fh_dentry(exp->ex_dentry->d_inode->i_sb, tfh, 3, fh->ofh_dirino?2:1, !(exp->ex_flags & NFSEXP_NOSUBTREECHECK)); } if (IS_ERR(dentry)) { if (PTR_ERR(dentry) != -EINVAL) error = nfserrno(PTR_ERR(dentry)); goto out; } #ifdef NFSD_PARANOIA if (S_ISDIR(dentry->d_inode->i_mode) && (dentry->d_flags & DCACHE_NFSD_DISCONNECTED)) { printk("nfsd: find_fh_dentry returned a DISCONNECTED directory: %s/%s\n", dentry->d_parent->d_name.name, dentry->d_name.name); } #endif fhp->fh_dentry = dentry; fhp->fh_export = exp; nfsd_nr_verified++; } else { /* just rechecking permissions * (e.g. nfsproc_create calls fh_verify, then nfsd_create does as well) */ dprintk("nfsd: fh_verify - just checking\n"); dentry = fhp->fh_dentry; exp = fhp->fh_export; } inode = dentry->d_inode; /* Type check. The correct error return for type mismatches * does not seem to be generally agreed upon. SunOS seems to * use EISDIR if file isn't S_IFREG; a comment in the NFSv3 * spec says this is incorrect (implementation notes for the * write call). */ /* When is type ever negative? */ if (type > 0 && (inode->i_mode & S_IFMT) != type) { error = (type == S_IFDIR)? nfserr_notdir : nfserr_isdir; goto out; } if (type < 0 && (inode->i_mode & S_IFMT) == -type) { error = (type == -S_IFDIR)? nfserr_notdir : nfserr_isdir; goto out; } /* * Security: Check that the export is valid for dentry */ error = 0; if (!(exp->ex_flags & NFSEXP_NOSUBTREECHECK)) { if (exp->ex_dentry != dentry) { struct dentry *tdentry = dentry; do { tdentry = tdentry->d_parent; if (exp->ex_dentry == tdentry) break; /* executable only by root and we can't be root */ if (current->fsuid && (exp->ex_flags & NFSEXP_ROOTSQUASH) && !(tdentry->d_inode->i_uid && (tdentry->d_inode->i_mode & S_IXUSR)) && !(tdentry->d_inode->i_gid && (tdentry->d_inode->i_mode & S_IXGRP)) && !(tdentry->d_inode->i_mode & S_IXOTH) ) { error = nfserr_stale; nfsdstats.fh_stale++; dprintk("fh_verify: no root_squashed access.\n"); } } while ((tdentry != tdentry->d_parent)); if (exp->ex_dentry != tdentry) { error = nfserr_stale; nfsdstats.fh_stale++; printk("nfsd Security: %s/%s bad export.\n", dentry->d_parent->d_name.name, dentry->d_name.name); goto out; } } } /* Finally, check access permissions. */ if (!error) { error = nfsd_permission(exp, dentry, access); } #ifdef NFSD_PARANOIA_EXTREME if (error) { printk("fh_verify: %s/%s permission failure, acc=%x, error=%d\n", dentry->d_parent->d_name.name, dentry->d_name.name, access, (error >> 24)); } #endif out: return error; } /* * Compose a file handle for an NFS reply. * * Note that when first composed, the dentry may not yet have * an inode. In this case a call to fh_update should be made * before the fh goes out on the wire ... */ inline int _fh_update(struct dentry *dentry, struct svc_export *exp, __u32 *datap, int *maxsize) { struct super_block *sb = dentry->d_inode->i_sb; if (dentry == exp->ex_dentry) { *maxsize = 0; return 0; } if (sb->s_op->dentry_to_fh) { int need_parent = !S_ISDIR(dentry->d_inode->i_mode) && !(exp->ex_flags & NFSEXP_NOSUBTREECHECK); int type = sb->s_op->dentry_to_fh(dentry, datap, maxsize, need_parent); return type; } if (*maxsize < 2) return 255; *datap++ = ino_t_to_u32(dentry->d_inode->i_ino); *datap++ = dentry->d_inode->i_generation; if (*maxsize ==2 || S_ISDIR(dentry->d_inode->i_mode) || (exp->ex_flags & NFSEXP_NOSUBTREECHECK)) { *maxsize = 2; return 1; } *datap++ = ino_t_to_u32(dentry->d_parent->d_inode->i_ino); *maxsize = 3; return 2; } /* * for composing old style file handles */ inline void _fh_update_old(struct dentry *dentry, struct svc_export *exp, struct knfsd_fh *fh) { fh->ofh_ino = ino_t_to_u32(dentry->d_inode->i_ino); fh->ofh_generation = dentry->d_inode->i_generation; if (S_ISDIR(dentry->d_inode->i_mode) || (exp->ex_flags & NFSEXP_NOSUBTREECHECK)) fh->ofh_dirino = 0; } int fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry, struct svc_fh *ref_fh) { /* ref_fh is a reference file handle. * if it is non-null, then we should compose a filehandle which is * of the same version, where possible. * Currently, that means that if ref_fh->fh_handle.fh_version == 0xca * Then create a 32byte filehandle using nfs_fhbase_old * But only do this if dentry_to_fh is not available * */ struct inode * inode = dentry->d_inode; struct dentry *parent = dentry->d_parent; __u32 *datap; dprintk("nfsd: fh_compose(exp %x/%ld %s/%s, ino=%ld)\n", exp->ex_dev, (long) exp->ex_ino, parent->d_name.name, dentry->d_name.name, (inode ? inode->i_ino : 0)); if (fhp->fh_locked || fhp->fh_dentry) { printk(KERN_ERR "fh_compose: fh %s/%s not initialized!\n", parent->d_name.name, dentry->d_name.name); } if (fhp->fh_maxsize < NFS_FHSIZE) printk(KERN_ERR "fh_compose: called with maxsize %d! %s/%s\n", fhp->fh_maxsize, parent->d_name.name, dentry->d_name.name); fhp->fh_dentry = dentry; /* our internal copy */ fhp->fh_export = exp; if (ref_fh && ref_fh->fh_handle.fh_version == 0xca && parent->d_inode->i_sb->s_op->dentry_to_fh == NULL) { /* old style filehandle please */ memset(&fhp->fh_handle.fh_base, 0, NFS_FHSIZE); fhp->fh_handle.fh_size = NFS_FHSIZE; fhp->fh_handle.ofh_dcookie = 0xfeebbaca; fhp->fh_handle.ofh_dev = htonl((MAJOR(exp->ex_dev)<<16)| MINOR(exp->ex_dev)); fhp->fh_handle.ofh_xdev = fhp->fh_handle.ofh_dev; fhp->fh_handle.ofh_xino = ino_t_to_u32(exp->ex_ino); fhp->fh_handle.ofh_dirino = ino_t_to_u32(dentry->d_parent->d_inode->i_ino); if (inode) _fh_update_old(dentry, exp, &fhp->fh_handle); } else { fhp->fh_handle.fh_version = 1; fhp->fh_handle.fh_auth_type = 0; fhp->fh_handle.fh_fsid_type = 0; datap = fhp->fh_handle.fh_auth+0; /* fsid_type 0 == 2byte major, 2byte minor, 4byte inode */ *datap++ = htonl((MAJOR(exp->ex_dev)<<16)| MINOR(exp->ex_dev)); *datap++ = ino_t_to_u32(exp->ex_ino); fhp->fh_handle.fh_size = 3*4; if (inode) { int size = fhp->fh_maxsize/4 - 3; fhp->fh_handle.fh_fileid_type = _fh_update(dentry, exp, datap, &size); fhp->fh_handle.fh_size += size*4; } } nfsd_nr_verified++; if (fhp->fh_handle.fh_fileid_type == 255) return nfserr_opnotsupp; return 0; } /* * Update file handle information after changing a dentry. * This is only called by nfsd_create, nfsd_create_v3 and nfsd_proc_create */ int fh_update(struct svc_fh *fhp) { struct dentry *dentry; __u32 *datap; if (!fhp->fh_dentry) goto out_bad; dentry = fhp->fh_dentry; if (!dentry->d_inode) goto out_negative; if (fhp->fh_handle.fh_version != 1) { _fh_update_old(dentry, fhp->fh_export, &fhp->fh_handle); } else { int size; if (fhp->fh_handle.fh_fileid_type != 0) goto out_uptodate; datap = fhp->fh_handle.fh_auth+ fhp->fh_handle.fh_size/4 -1; size = (fhp->fh_maxsize - fhp->fh_handle.fh_size)/4; fhp->fh_handle.fh_fileid_type = _fh_update(dentry, fhp->fh_export, datap, &size); fhp->fh_handle.fh_size += size*4; } out: return 0; out_bad: printk(KERN_ERR "fh_update: fh not verified!\n"); goto out; out_negative: printk(KERN_ERR "fh_update: %s/%s still negative!\n", dentry->d_parent->d_name.name, dentry->d_name.name); goto out; out_uptodate: printk(KERN_ERR "fh_update: %s/%s already up-to-date!\n", dentry->d_parent->d_name.name, dentry->d_name.name); goto out; } /* * Release a file handle. */ void fh_put(struct svc_fh *fhp) { struct dentry * dentry = fhp->fh_dentry; if (dentry) { fh_unlock(fhp); fhp->fh_dentry = NULL; dput(dentry); nfsd_nr_put++; } return; }