/* * linux/fs/nfsd/nfsfh.c * * NFS server filehandle treatment. * * Copyright (C) 1995, 1996 Olaf Kirch */ #include #include #include #include #include #include #include #include #include #define NFSDDBG_FACILITY NFSDDBG_FH #define NFSD_PARANOIA 1 /* #define NFSD_DEBUG_VERBOSE 1 */ extern unsigned long num_physpages; #define NFSD_FILE_CACHE 0 #define NFSD_DIR_CACHE 1 struct fh_entry { struct dentry * dentry; unsigned long reftime; ino_t ino; dev_t dev; }; #define NFSD_MAXFH PAGE_SIZE/sizeof(struct fh_entry) static struct fh_entry filetable[NFSD_MAXFH]; static struct fh_entry dirstable[NFSD_MAXFH]; static int nfsd_nr_verified = 0; static int nfsd_nr_put = 0; static unsigned long nfsd_next_expire = 0; static int add_to_fhcache(struct dentry *, int); static int nfsd_d_validate(struct dentry *); struct dentry * lookup_inode(dev_t, ino_t, ino_t); static LIST_HEAD(fixup_head); static LIST_HEAD(path_inuse); static int nfsd_nr_fixups = 0; static int nfsd_nr_paths = 0; #define NFSD_MAX_PATHS 500 #define NFSD_MAX_FIXUPAGE 60*HZ struct nfsd_fixup { struct list_head lru; ino_t dir; ino_t ino; dev_t dev; struct dentry *dentry; unsigned long reftime; }; struct nfsd_path { struct list_head lru; unsigned long reftime; int users; ino_t ino; dev_t dev; char name[1]; }; static struct nfsd_fixup * find_cached_lookup(dev_t dev, ino_t dir, ino_t ino) { struct list_head *tmp = fixup_head.next; for (; tmp != &fixup_head; tmp = tmp->next) { struct nfsd_fixup *fp; fp = list_entry(tmp, struct nfsd_fixup, lru); if (fp->ino != ino) continue; if (fp->dir != dir) continue; if (fp->dev != dev) continue; list_del(tmp); list_add(tmp, &fixup_head); fp->reftime = jiffies; return fp; } return NULL; } /* * Save the dentry pointer from a successful lookup. */ static void add_to_lookup_cache(struct dentry *dentry, struct knfs_fh *fh) { struct nfsd_fixup *fp; fp = find_cached_lookup(fh->fh_dev, fh->fh_dirino, fh->fh_ino); if (fp) { fp->dentry = dentry; return; } /* * Add a new entry. The small race here is unimportant: * if another task adds the same lookup, both entries * will be consistent. */ fp = kmalloc(sizeof(struct nfsd_fixup), GFP_KERNEL); if (fp) { fp->dir = fh->fh_dirino; fp->ino = fh->fh_ino; fp->dev = fh->fh_dev; fp->dentry = dentry; fp->reftime = jiffies; list_add(&fp->lru, &fixup_head); nfsd_nr_fixups++; } } static void free_fixup_entry(struct nfsd_fixup *fp) { list_del(&fp->lru); kfree(fp); nfsd_nr_fixups--; } /* * Copy a dentry's path into the specified buffer. */ static int copy_path(char *buffer, struct dentry *dentry, int namelen) { char *p, *b = buffer; int result = 0, totlen = 0, len; while (1) { struct dentry *parent; dentry = dentry->d_covers; parent = dentry->d_parent; len = dentry->d_name.len; p = (char *) dentry->d_name.name + len; totlen += len; if (totlen > namelen) goto out; while (len--) *b++ = *(--p); if (dentry == parent) break; dentry = parent; totlen++; if (totlen > namelen) goto out; *b++ = '/'; } *b = 0; /* * Now reverse in place ... */ p = buffer; while (p < b) { char c = *(--b); *b = *p; *p++ = c; } result = 1; out: return result; } /* * Add a dentry's path to the path cache. */ static int add_to_path_cache(struct dentry *dentry) { struct inode *inode = dentry->d_inode; struct dentry *this; struct nfsd_path *new; int len, result = 0; #ifdef NFSD_DEBUG_VERBOSE printk("add_to_path_cache: cacheing %s/%s\n", dentry->d_parent->d_name.name, dentry->d_name.name); #endif /* * Get the length of the full pathname. */ restart: len = 0; this = dentry; while (1) { struct dentry *parent; this = this->d_covers; parent = this->d_parent; len += this->d_name.len; if (this == parent) break; this = parent; len++; } /* * Allocate a structure to hold the path. */ new = kmalloc(sizeof(struct nfsd_path) + len, GFP_KERNEL); if (new) { new->users = 0; new->reftime = jiffies; new->ino = inode->i_ino; new->dev = inode->i_dev; result = copy_path(new->name, dentry, len); if (!result) goto retry; list_add(&new->lru, &path_inuse); nfsd_nr_paths++; #ifdef NFSD_DEBUG_VERBOSE printk("add_to_path_cache: added %s, paths=%d\n", new->name, nfsd_nr_paths); #endif } return result; /* * If the dentry's path length changed, just try again ... */ retry: kfree(new); printk("add_to_path_cache: path length changed, retrying\n"); goto restart; } /* * Search for a path entry for the specified (dev, inode). */ struct nfsd_path *get_path_entry(dev_t dev, ino_t ino) { struct nfsd_path *pe; struct list_head *tmp; for (tmp = path_inuse.next; tmp != &path_inuse; tmp = tmp->next) { pe = list_entry(tmp, struct nfsd_path, lru); if (pe->ino != ino) continue; if (pe->dev != dev) continue; list_del(tmp); list_add(tmp, &path_inuse); pe->users++; pe->reftime = jiffies; #ifdef NFSD_PARANOIA printk("get_path_entry: found %s for %s/%ld\n", pe->name, kdevname(dev), ino); #endif return pe; } return NULL; } static void put_path(struct nfsd_path *pe) { pe->users--; } static void free_path_entry(struct nfsd_path *pe) { if (pe->users) printk("free_path_entry: %s in use, users=%d\n", pe->name, pe->users); list_del(&pe->lru); kfree(pe); nfsd_nr_paths--; } struct nfsd_getdents_callback { struct nfsd_dirent *dirent; ino_t dirino; /* parent inode number */ int found; /* dirent inode matched? */ int sequence; /* sequence counter */ }; struct nfsd_dirent { ino_t ino; /* preset to desired entry */ int len; char name[256]; }; /* * A rather strange filldir function to capture the inode number * for the second entry (the parent inode) and the name matching * the specified inode number. */ static int filldir_one(void * __buf, const char * name, int len, off_t pos, ino_t ino) { struct nfsd_getdents_callback *buf = __buf; struct nfsd_dirent *dirent = buf->dirent; int result = 0; buf->sequence++; #ifdef NFSD_DEBUG_VERBOSE printk("filldir_one: seq=%d, ino=%ld, name=%s\n", buf->sequence, ino, name); #endif if (buf->sequence == 2) { buf->dirino = ino; goto out; } if (dirent->ino == ino) { dirent->len = len; memcpy(dirent->name, name, len); dirent->name[len] = 0; buf->found = 1; result = -1; } out: return result; } /* * Read a directory and return the parent inode number and the name * of the specified entry. The dirent must be initialized with the * inode number of the desired entry. */ static int get_parent_ino(struct dentry *dentry, struct nfsd_dirent *dirent) { 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_op || !dir->i_op->default_file_ops) 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.dirent = dirent; buffer.dirino = 0; buffer.found = 0; buffer.sequence = 0; while (1) { int old_seq = buffer.sequence; down(&dir->i_sem); error = file.f_op->readdir(&file, &buffer, filldir_one); up(&dir->i_sem); if (error < 0) break; error = 0; if (buffer.found) break; error = -ENOENT; if (old_seq == buffer.sequence) break; } dirent->ino = buffer.dirino; out_close: if (file.f_op->release) file.f_op->release(dir, &file); out: return error; } /* * Look up a dentry given inode and parent inode numbers. * * This relies on the ability of a unix-like filesystem to return * the parent inode of a directory as the ".." (second) entry. * * This could be further optimized if we had an efficient way of * searching for a dentry given the inode: as we walk up the tree, * it's likely that a dentry exists before we reach the root. */ struct dentry * lookup_inode(dev_t dev, ino_t dirino, ino_t ino) { struct super_block *sb; struct dentry *root, *dentry, *result; struct inode *dir; char *name; unsigned long page; ino_t root_ino; int error; struct nfsd_dirent dirent; result = ERR_PTR(-ENOMEM); page = __get_free_page(GFP_KERNEL); if (!page) goto out; /* * Get the root dentry for the device. */ result = ERR_PTR(-ENOENT); sb = get_super(dev); if (!sb) goto out_page; root = dget(sb->s_root); root_ino = root->d_inode->i_ino; /* usually 2 */ name = (char *) page + PAGE_SIZE; *(--name) = 0; /* * Walk up the tree to construct the name string. * When we reach the root inode, look up the name * relative to the root dentry. */ while (1) { if (ino == root_ino) { if (*name == '/') name++; /* * Note: this dput()s the root dentry. */ result = lookup_dentry(name, root, 0); break; } result = ERR_PTR(-ENOENT); dir = iget(sb, dirino); if (!dir) goto out_root; dentry = d_alloc_root(dir, NULL); if (!dentry) goto out_iput; /* * Get the name for this inode and the next parent inode. */ dirent.ino = ino; error = get_parent_ino(dentry, &dirent); result = ERR_PTR(error); dput(dentry); if (error) goto out_root; /* * Prepend the name to the buffer. */ result = ERR_PTR(-ENAMETOOLONG); name -= (dirent.len + 1); if ((unsigned long) name <= page) goto out_root; memcpy(name + 1, dirent.name, dirent.len); *name = '/'; /* * Make sure we can't get caught in a loop ... */ if (dirino == dirent.ino && dirino != root_ino) { printk("lookup_inode: looping?? (ino=%ld, path=%s)\n", dirino, name); goto out_root; } ino = dirino; dirino = dirent.ino; } out_page: free_page(page); out: return result; /* * Error exits ... */ out_iput: result = ERR_PTR(-ENOMEM); iput(dir); out_root: dput(root); goto out; } /* * Find an entry in the cache matching the given dentry pointer. */ static struct fh_entry *find_fhe(struct dentry *dentry, int cache, struct fh_entry **empty) { struct fh_entry *fhe; int i, found = (empty == NULL) ? 1 : 0; fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0]; for (i = 0; i < NFSD_MAXFH; i++, fhe++) { if (fhe->dentry == dentry) { fhe->reftime = jiffies; return fhe; } if (!found && !fhe->dentry) { found = 1; *empty = fhe; } } return NULL; } /* * Expire a cache entry. */ static void expire_fhe(struct fh_entry *empty, int cache) { struct dentry *dentry = empty->dentry; #ifdef NFSD_DEBUG_VERBOSE printk("expire_fhe: expiring %s/%s, d_count=%d, ino=%ld\n", dentry->d_parent->d_name.name, dentry->d_name.name, dentry->d_count,empty->ino); #endif empty->dentry = NULL; /* no dentry */ /* * Add the parent to the dir cache before releasing the dentry, * and check whether to save a copy of the dentry's path. */ if (dentry != dentry->d_parent) { struct dentry *parent = dget(dentry->d_parent); if (add_to_fhcache(parent, NFSD_DIR_CACHE)) nfsd_nr_verified++; else dput(parent); /* * If we're expiring a directory, copy its path. */ if (cache == NFSD_DIR_CACHE) { add_to_path_cache(dentry); } } dput(dentry); nfsd_nr_put++; } /* * Look for an empty slot, or select one to expire. */ static void expire_slot(int cache) { struct fh_entry *fhe, *empty = NULL; unsigned long oldest = -1; int i; fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0]; for (i = 0; i < NFSD_MAXFH; i++, fhe++) { if (!fhe->dentry) goto out; if (fhe->reftime < oldest) { oldest = fhe->reftime; empty = fhe; } } if (empty) expire_fhe(empty, cache); out: return; } /* * Expire any cache entries older than a certain age. */ static void expire_old(int cache, int age) { struct list_head *tmp; struct fh_entry *fhe; int i; #ifdef NFSD_DEBUG_VERBOSE printk("expire_old: expiring %s older than %d\n", (cache == NFSD_FILE_CACHE) ? "file" : "dir", age); #endif fhe = (cache == NFSD_FILE_CACHE) ? &filetable[0] : &dirstable[0]; for (i = 0; i < NFSD_MAXFH; i++, fhe++) { if (!fhe->dentry) continue; if ((jiffies - fhe->reftime) > age) expire_fhe(fhe, cache); } /* * Remove old entries from the patch-up cache. */ while ((tmp = fixup_head.prev) != &fixup_head) { struct nfsd_fixup *fp; fp = list_entry(tmp, struct nfsd_fixup, lru); if ((jiffies - fp->reftime) < NFSD_MAX_FIXUPAGE) break; free_fixup_entry(fp); } /* * Trim the path cache ... */ while (nfsd_nr_paths > NFSD_MAX_PATHS) { struct nfsd_path *pe; pe = list_entry(path_inuse.prev, struct nfsd_path, lru); if (pe->users) break; free_path_entry(pe); } } /* * Add a dentry to the file or dir cache. * * Note: As NFS filehandles must have an inode, we don't accept * negative dentries. */ static int add_to_fhcache(struct dentry *dentry, int cache) { struct fh_entry *fhe, *empty = NULL; struct inode *inode = dentry->d_inode; if (!inode) { #ifdef NFSD_PARANOIA printk("add_to_fhcache: %s/%s rejected, no inode!\n", dentry->d_parent->d_name.name, dentry->d_name.name); #endif return 0; } repeat: fhe = find_fhe(dentry, cache, &empty); if (fhe) { return 0; } /* * Not found ... make a new entry. */ if (empty) { empty->dentry = dentry; empty->reftime = jiffies; empty->ino = inode->i_ino; empty->dev = inode->i_dev; return 1; } expire_slot(cache); goto repeat; } /* * Find an entry in the dir cache for the specified inode number. */ static struct fh_entry *find_fhe_by_ino(dev_t dev, ino_t ino) { struct fh_entry * fhe = &dirstable[0]; int i; for (i = 0; i < NFSD_MAXFH; i++, fhe++) { if (fhe->ino == ino && fhe->dev == dev) { fhe->reftime = jiffies; return fhe; } } return NULL; } /* * Find the (directory) dentry with the specified (dev, inode) number. * Note: this leaves the dentry in the cache. */ static struct dentry *find_dentry_by_ino(dev_t dev, ino_t ino) { struct fh_entry *fhe; struct nfsd_path *pe; struct dentry * dentry; #ifdef NFSD_DEBUG_VERBOSE printk("find_dentry_by_ino: looking for inode %ld\n", ino); #endif /* * Special case: inode number 2 is the root inode, * so we can use the root dentry for the device. */ if (ino == 2) { struct super_block *sb = get_super(dev); if (sb) { #ifdef NFSD_PARANOIA printk("find_dentry_by_ino: getting root dentry for %s\n", kdevname(dev)); #endif if (sb->s_root) { dentry = dget(sb->s_root); goto out; } else { #ifdef NFSD_PARANOIA printk("find_dentry_by_ino: %s has no root??\n", kdevname(dev)); #endif } } } /* * Search the dentry cache ... */ fhe = find_fhe_by_ino(dev, ino); if (fhe) { dentry = dget(fhe->dentry); goto out; } /* * Search the path cache ... */ dentry = NULL; pe = get_path_entry(dev, ino); if (pe) { struct dentry *res; res = lookup_dentry(pe->name, NULL, 0); if (!IS_ERR(res)) { struct inode *inode = res->d_inode; if (inode && inode->i_ino == ino && inode->i_dev == dev) { dentry = res; #ifdef NFSD_PARANOIA printk("find_dentry_by_ino: found %s/%s, ino=%ld\n", dentry->d_parent->d_name.name, dentry->d_name.name, ino); #endif if (add_to_fhcache(dentry, NFSD_DIR_CACHE)) { dget(dentry); nfsd_nr_verified++; } } else { dput(res); } } else { #ifdef NFSD_PARANOIA printk("find_dentry_by_ino: %s lookup failed\n", pe->name); #endif } put_path(pe); } out: return dentry; } /* * Look for an entry in the file cache matching the dentry pointer, * and verify that the (dev, inode) numbers are correct. If found, * the entry is removed from the cache. */ static struct dentry *find_dentry_in_fhcache(struct knfs_fh *fh) { struct fh_entry * fhe; fhe = find_fhe(fh->fh_dcookie, NFSD_FILE_CACHE, NULL); if (fhe) { struct dentry *parent, *dentry = fhe->dentry; struct inode *inode = dentry->d_inode; if (!inode) { #ifdef NFSD_PARANOIA printk("find_dentry_in_fhcache: %s/%s has no inode!\n", dentry->d_parent->d_name.name, dentry->d_name.name); #endif goto out; } if (inode->i_ino != fh->fh_ino || inode->i_dev != fh->fh_dev) goto out; fhe->dentry = NULL; fhe->ino = 0; fhe->dev = 0; nfsd_nr_put++; /* * Make sure the parent is in the dir cache ... */ parent = dget(dentry->d_parent); if (add_to_fhcache(parent, NFSD_DIR_CACHE)) nfsd_nr_verified++; else dput(parent); return dentry; } out: return NULL; } /* * Look for an entry in the parent directory with the specified * inode number. */ static struct dentry *lookup_by_inode(struct dentry *parent, ino_t ino) { struct dentry *dentry; int error; struct nfsd_dirent dirent; /* * Search the directory for the inode number. */ dirent.ino = ino; error = get_parent_ino(parent, &dirent); if (error) { #ifdef NFSD_PARANOIA printk("lookup_by_inode: ino %ld not found in %s\n", ino, parent->d_name.name); #endif goto no_entry; } #ifdef NFSD_PARANOIA printk("lookup_by_inode: found %s\n", dirent.name); #endif dentry = lookup_dentry(dirent.name, dget(parent), 0); if (!IS_ERR(dentry)) { if (dentry->d_inode && dentry->d_inode->i_ino == ino) goto out; #ifdef NFSD_PARANOIA printk("lookup_by_inode: %s/%s inode mismatch??\n", parent->d_name.name, dentry->d_name.name); #endif dput(dentry); } else { #ifdef NFSD_PARANOIA printk("lookup_by_inode: %s lookup failed, error=%ld\n", dirent.name, PTR_ERR(dentry)); #endif } no_entry: dentry = NULL; out: return dentry; } /* * Search the fix-up list for a dentry from a prior lookup. */ static struct dentry *nfsd_cached_lookup(struct knfs_fh *fh) { struct nfsd_fixup *fp; fp = find_cached_lookup(fh->fh_dev, fh->fh_dirino, fh->fh_ino); if (fp) return fp->dentry; return NULL; } /* * The is the basic lookup mechanism for turning an NFS filehandle * into a dentry. There are several levels to the search: * (1) Look for the dentry pointer the short-term fhcache, * and verify that it has the correct inode number. * * (2) Try to validate the dentry pointer in the filehandle, * and verify that it has the correct inode number. If this * fails, check for a cached lookup in the fix-up list and * repeat step (2) using the new dentry pointer. * * (3) Look up the dentry by using the inode and parent inode numbers * to build the name string. This should succeed for any unix-like * filesystem. * * (4) Search for the parent dentry in the dir cache, and then * look for the name matching the inode number. * * (5) The most general case ... search the whole volume for the inode. * * If successful, we return a dentry with the use count incremented. * * Note: steps (4) and (5) above are probably unnecessary now that (3) * is working. Remove the code once this is verified ... */ static struct dentry * find_fh_dentry(struct knfs_fh *fh) { struct dentry *dentry, *parent; int looked_up = 0, retry = 0; /* * Stage 1: Look for the dentry in the short-term fhcache. */ dentry = find_dentry_in_fhcache(fh); if (dentry) { nfsdstats.fh_cached++; goto out; } /* * Stage 2: Attempt to validate the dentry in the filehandle. */ dentry = fh->fh_dcookie; recheck: if (nfsd_d_validate(dentry)) { struct inode * dir = dentry->d_parent->d_inode; if (dir->i_ino == fh->fh_dirino && dir->i_dev == fh->fh_dev) { struct inode * inode = dentry->d_inode; /* * NFS filehandles must always have an inode, * so we won't accept a negative dentry. */ if (inode && inode->i_ino == fh->fh_ino) { dget(dentry); #ifdef NFSD_DEBUG_VERBOSE printk("find_fh_dentry: validated %s/%s, ino=%ld\n", dentry->d_parent->d_name.name, dentry->d_name.name, inode->i_ino); #endif if (!retry) nfsdstats.fh_valid++; else { nfsdstats.fh_fixup++; #ifdef NFSD_DEBUG_VERBOSE printk("find_fh_dentry: retried validation successful\n"); #endif } goto out; } } } /* * Before proceeding to a lookup, check whether we cached a * prior lookup. If so, try to validate that dentry ... */ if (!retry && (dentry = nfsd_cached_lookup(fh)) != NULL) { retry = 1; goto recheck; } /* * Stage 3: Look up the dentry based on the inode and parent inode * numbers. This should work for all unix-like filesystems ... */ looked_up = 1; dentry = lookup_inode(fh->fh_dev, fh->fh_dirino, fh->fh_ino); if (!IS_ERR(dentry)) { struct inode * inode = dentry->d_inode; #ifdef NFSD_DEBUG_VERBOSE printk("find_fh_dentry: looked up %s/%s\n", dentry->d_parent->d_name.name, dentry->d_name.name); #endif if (inode && inode->i_ino == fh->fh_ino) { nfsdstats.fh_lookup++; goto out; } #ifdef NFSD_PARANOIA printk("find_fh_dentry: %s/%s lookup mismatch!\n", dentry->d_parent->d_name.name, dentry->d_name.name); #endif dput(dentry); } /* * Stage 4: Look for the parent dentry in the fhcache ... */ parent = find_dentry_by_ino(fh->fh_dev, fh->fh_dirino); if (parent) { /* * ... then search for the inode in the parent directory. */ dentry = lookup_by_inode(parent, fh->fh_ino); dput(parent); if (dentry) goto out; } /* * Stage 5: Search the whole volume. */ #ifdef NFSD_PARANOIA printk("find_fh_dentry: %s, %ld/%ld not found -- need full search!\n", kdevname(fh->fh_dev), fh->fh_dirino, fh->fh_ino); #endif dentry = NULL; nfsdstats.fh_stale++; out: if (looked_up && dentry) { add_to_lookup_cache(dentry, fh); } /* * Perform any needed housekeeping ... * N.B. move this into one of the daemons ... */ if (jiffies >= nfsd_next_expire) { expire_old(NFSD_FILE_CACHE, 5*HZ); expire_old(NFSD_DIR_CACHE , 60*HZ); nfsd_next_expire = jiffies + 5*HZ; } return dentry; } /* * Perform sanity checks on the dentry in a client's file handle. * * Note that the filehandle dentry may need to be freed even after * an error return. */ u32 fh_verify(struct svc_rqst *rqstp, struct svc_fh *fhp, int type, int access) { struct svc_export *exp; struct dentry *dentry; struct inode *inode; struct knfs_fh *fh = &fhp->fh_handle; u32 error = 0; if(fhp->fh_dverified) goto out; dprintk("nfsd: fh_lookup(exp %x/%ld fh %p)\n", fh->fh_xdev, fh->fh_xino, fh->fh_dcookie); /* * Look up the export entry. * N.B. We need to lock this while in use ... */ error = nfserr_stale; exp = exp_get(rqstp->rq_client, fh->fh_xdev, fh->fh_xino); if (!exp) /* export entry revoked */ 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 (%08lx:%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 fh. */ error = nfserr_stale; dentry = find_fh_dentry(fh); if (!dentry) goto out; /* * Note: it's possible that the returned dentry won't be the * one in the filehandle. We can correct the FH for our use, * but unfortunately the client will keep sending the broken * one. Hopefully the lookup will keep patching things up.. */ fhp->fh_dentry = dentry; fhp->fh_export = exp; fhp->fh_dverified = 1; nfsd_nr_verified++; /* 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). */ inode = dentry->d_inode; 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; } /* Finally, check access permissions. */ error = nfsd_permission(fhp->fh_export, dentry, access); 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); out: return error; } /* * Compose a filehandle 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 ... */ void fh_compose(struct svc_fh *fhp, struct svc_export *exp, struct dentry *dentry) { struct inode * inode = dentry->d_inode; dprintk("nfsd: fh_compose(exp %x/%ld dentry %p)\n", exp->ex_dev, exp->ex_ino, dentry); /* * N.B. We shouldn't need to init the fh -- the call to fh_compose * may not be done on error paths, but the cleanup must call fh_put. * Fix this soon! */ fh_init(fhp); fhp->fh_handle.fh_dcookie = dentry; if (inode) { fhp->fh_handle.fh_ino = inode->i_ino; } fhp->fh_handle.fh_dirino = dentry->d_parent->d_inode->i_ino; fhp->fh_handle.fh_dev = dentry->d_parent->d_inode->i_dev; fhp->fh_handle.fh_xdev = exp->ex_dev; fhp->fh_handle.fh_xino = exp->ex_ino; fhp->fh_dentry = dentry; /* our internal copy */ fhp->fh_export = exp; /* We stuck it there, we know it's good. */ fhp->fh_dverified = 1; nfsd_nr_verified++; } /* * Update filehandle information after changing a dentry. */ void fh_update(struct svc_fh *fhp) { struct dentry *dentry; struct inode *inode; if (!fhp->fh_dverified) { printk("fh_update: fh not verified!\n"); goto out; } dentry = fhp->fh_dentry; inode = dentry->d_inode; if (!inode) { printk("fh_update: %s/%s still negative!\n", dentry->d_parent->d_name.name, dentry->d_name.name); goto out; } fhp->fh_handle.fh_ino = inode->i_ino; out: return; } /* * Release a filehandle. If the filehandle carries a dentry count, * we add the dentry to the short-term cache rather than release it. */ void fh_put(struct svc_fh *fhp) { if (fhp->fh_dverified) { struct dentry * dentry = fhp->fh_dentry; fh_unlock(fhp); fhp->fh_dverified = 0; if (!dentry->d_count) { printk("fh_put: %s/%s has d_count 0!\n", dentry->d_parent->d_name.name, dentry->d_name.name); return; } if (!dentry->d_inode || !add_to_fhcache(dentry, 0)) { dput(dentry); nfsd_nr_put++; } } } /* * Verify that the FH dentry is still a valid dentry pointer. * After making some preliminary checks, we ask VFS to verify * that it is indeed a dentry. */ static int nfsd_d_validate(struct dentry *dentry) { unsigned long dent_addr = (unsigned long) dentry; unsigned long min_addr = PAGE_OFFSET; unsigned long max_addr = min_addr + (num_physpages << PAGE_SHIFT); unsigned long align_mask = 0x0F; unsigned int len; int valid = 0; if (dent_addr < min_addr) goto bad_addr; if (dent_addr > max_addr - sizeof(struct dentry)) goto bad_addr; if ((dent_addr & ~align_mask) != dent_addr) goto bad_align; /* * Looks safe enough to dereference ... */ len = dentry->d_name.len; if (len > NFS_MAXNAMLEN) goto out; /* * Note: d_validate doesn't dereference the parent pointer ... * just combines it with the name hash to find the hash chain. */ valid = d_validate(dentry, dentry->d_parent, dentry->d_name.hash, len); out: return valid; bad_addr: printk("nfsd_d_validate: invalid address %lx\n", dent_addr); goto out; bad_align: printk("nfsd_d_validate: unaligned address %lx\n", dent_addr); goto out; } /* * Flush any cached dentries for the specified device * or for all devices. * * This is called when revoking the last export for a * device, so that it can be unmounted cleanly. */ void nfsd_fh_flush(dev_t dev) { struct fh_entry *fhe; int i, pass = 2; fhe = &filetable[0]; while (pass--) { for (i = 0; i < NFSD_MAXFH; i++, fhe++) { struct dentry *dentry = fhe->dentry; if (!dentry) continue; if (dev && dentry->d_inode->i_dev != dev) continue; fhe->dentry = NULL; dput(dentry); nfsd_nr_put++; } fhe = &dirstable[0]; } } /* * Free the dentry and path caches. */ void nfsd_fh_free(void) { struct list_head *tmp; int i; /* Flush dentries for all devices */ nfsd_fh_flush(0); /* * N.B. write a destructor for these lists ... */ i = 0; while ((tmp = fixup_head.next) != &fixup_head) { struct nfsd_fixup *fp; fp = list_entry(tmp, struct nfsd_fixup, lru); free_fixup_entry(fp); i++; } printk("nfsd_fh_free: %d fixups freed\n", i); i = 0; while ((tmp = path_inuse.next) != &path_inuse) { struct nfsd_path *pe; pe = list_entry(tmp, struct nfsd_path, lru); free_path_entry(pe); i++; } printk("nfsd_fh_free: %d paths freed\n", i); printk("nfsd_fh_free: verified %d, put %d\n", nfsd_nr_verified, nfsd_nr_put); } void nfsd_fh_init(void) { memset(filetable, 0, NFSD_MAXFH*sizeof(struct fh_entry)); memset(dirstable, 0, NFSD_MAXFH*sizeof(struct fh_entry)); INIT_LIST_HEAD(&path_inuse); INIT_LIST_HEAD(&fixup_head); printk("nfsd_init: initialized fhcache, entries=%lu\n", NFSD_MAXFH); }